midi 0.1.1 → 0.1.2
raw patch · 25 files changed
+407/−431 lines, 25 filesdep +explicit-exception
Dependencies added: explicit-exception
Files
- midi.cabal +4/−2
- src/Sound/MIDI/File/Event.hs +2/−2
- src/Sound/MIDI/File/Event/Meta.hs +2/−2
- src/Sound/MIDI/File/Event/SystemExclusive.hs +1/−1
- src/Sound/MIDI/File/Load.hs +50/−52
- src/Sound/MIDI/Manufacturer.hs +1/−1
- src/Sound/MIDI/Message.hs +8/−9
- src/Sound/MIDI/Message/Channel.hs +7/−7
- src/Sound/MIDI/Message/Channel/Mode.hs +9/−5
- src/Sound/MIDI/Message/Channel/Voice.hs +1/−1
- src/Sound/MIDI/Message/System.hs +7/−7
- src/Sound/MIDI/Message/System/Common.hs +5/−4
- src/Sound/MIDI/Message/System/Exclusive.hs +17/−13
- src/Sound/MIDI/Message/System/RealTime.hs +1/−1
- src/Sound/MIDI/Parser/ByteString.hs +31/−58
- src/Sound/MIDI/Parser/Class.hs +87/−69
- src/Sound/MIDI/Parser/Exception.hs +24/−0
- src/Sound/MIDI/Parser/File.hs +20/−15
- src/Sound/MIDI/Parser/Primitive.hs +14/−14
- src/Sound/MIDI/Parser/Restricted.hs +42/−26
- src/Sound/MIDI/Parser/State.hs +0/−56
- src/Sound/MIDI/Parser/Status.hs +13/−8
- src/Sound/MIDI/Parser/Stream.hs +31/−77
- src/Sound/MIDI/Parser/Warning.hs +28/−0
- test/Main.hs +2/−1
midi.cabal view
@@ -1,5 +1,5 @@ Name: midi-Version: 0.1.1+Version: 0.1.2 License: GPL License-File: LICENSE Author: Henning Thielemann <haskell@henning-thielemann.de>@@ -29,6 +29,7 @@ Library Build-Depends: event-list >=0.0.6 && < 0.1, non-negative>=0.0.1 && <0.1+ Build-Depends: explicit-exception >=0.0.1 && <0.1 Build-Depends: bytestring >=0.9.0.1 && <0.10, binary >=0.4.2 && <0.5 Build-Depends: mtl >=1 && <2, QuickCheck >=1 && <2 If flag(splitBase)@@ -66,7 +67,8 @@ -- Parser class and general parser functions Sound.MIDI.Parser.Class Sound.MIDI.Parser.Restricted- Sound.MIDI.Parser.State+ Sound.MIDI.Parser.Exception+ Sound.MIDI.Parser.Warning Sound.MIDI.Parser.Primitive Sound.MIDI.Parser.Status -- concrete Parsers
src/Sound/MIDI/File/Event.hs view
@@ -84,7 +84,7 @@ -- * serialization -get :: Parser.C parser => StatusParser.T parser T+get :: Parser.C parser => Parser.Fallible (StatusParser.T parser) T get = StatusParser.lift get1 >>= \tag -> if tag < 0xF0@@ -101,7 +101,7 @@ last event and the current event. Parse a time and an event, ignoring System Exclusive messages. -}-getTrackEvent :: Parser.C parser => StatusParser.T parser TrackEvent+getTrackEvent :: Parser.C parser => Parser.Fallible (StatusParser.T parser) TrackEvent getTrackEvent = liftM2 (,) (StatusParser.lift getVar) get
src/Sound/MIDI/File/Event/Meta.hs view
@@ -156,11 +156,11 @@ -- * serialization -get :: Parser.C parser => parser T+get :: Parser.C parser => Parser.Fallible parser T get = do code <- get1 len <- getVar- let parse = ParserRestricted.run len+ let parse = ParserRestricted.runFallible len let returnText cons = liftM (cons . listCharFromByte) $ getBigN len case code of 000 -> parse $ liftM SequenceNum get2
src/Sound/MIDI/File/Event/SystemExclusive.hs view
@@ -30,7 +30,7 @@ deriving (Show, Eq, Ord) -get :: Parser.C parser => Int -> parser T+get :: Parser.C parser => Int -> Parser.Fallible parser T get tag = case tag of 0xF0 -> liftM Regular $ getBigN =<< getVar
src/Sound/MIDI/File/Load.hs view
@@ -29,22 +29,22 @@ -- import qualified Sound.MIDI.Bit as Bit import Sound.MIDI.String (unlinesS) import Sound.MIDI.Parser.Primitive-import Sound.MIDI.Parser.Class (PossiblyIncomplete, ) import qualified Sound.MIDI.Parser.Class as Parser-import qualified Sound.MIDI.Parser.Restricted as ParserRestricted-import qualified Sound.MIDI.Parser.ByteString as ParserByteString-import qualified Sound.MIDI.Parser.Stream as ParserStream-import qualified Sound.MIDI.Parser.File as ParserFile-import qualified Sound.MIDI.Parser.State as ParserState+import qualified Sound.MIDI.Parser.Restricted as RestrictedParser+import qualified Sound.MIDI.Parser.ByteString as ByteStringParser+import qualified Sound.MIDI.Parser.Stream as StreamParser+import qualified Sound.MIDI.Parser.File as FileParser import qualified Sound.MIDI.Parser.Status as StatusParser import qualified Sound.MIDI.Parser.Report as Report import qualified Control.Monad.State as State-import Control.Monad (liftM, liftM2, when, )+import Control.Monad.Trans (lift, )+import Control.Monad (liftM, liftM2, ) import qualified Data.ByteString.Lazy as B +import qualified Control.Monad.Exception.Asynchronous as Async+-- import qualified Control.Monad.Exception.Synchronous as Sync -- import System.IO (hPutStrLn, stderr, )-import Sound.MIDI.Utility (mapSnd, ) import Data.List (genericReplicate, genericLength, ) import Data.Maybe (catMaybes, ) @@ -54,22 +54,22 @@ Warnings are written to standard error output and an error is signaled by a user exception. This function will not be appropriate in GUI applications.-For these, use 'fromByteList' instead.+For these, use 'maybeFromByteString' instead. -} fromFile :: FilePath -> IO MIDIFile.T fromFile =- ParserFile.runIncompleteFile parse+ FileParser.runIncompleteFile parse {- fromFile :: FilePath -> IO MIDIFile.T fromFile filename = do report <- fmap maybeFromByteList $ readBinaryFile filename- mapM_ (hPutStrLn stderr . ("MIDI.File.Load warning: " ++)) (ParserStream.warnings report)+ mapM_ (hPutStrLn stderr . ("MIDI.File.Load warning: " ++)) (StreamParser.warnings report) either (ioError . userError . ("MIDI.File.Load error: " ++)) return- (ParserStream.result report)+ (StreamParser.result report) -} {- |@@ -86,12 +86,12 @@ maybeFromByteList :: ByteList -> Report.T MIDIFile.T maybeFromByteList =- ParserStream.runIncomplete parse . ParserStream.ByteList+ StreamParser.runIncomplete parse . StreamParser.ByteList maybeFromByteString :: B.ByteString -> Report.T MIDIFile.T maybeFromByteString =- ParserByteString.runIncomplete parse+ ByteStringParser.runIncomplete parse @@ -102,32 +102,30 @@ any non-header chunks that come before a header chunk. The header tells us the number of tracks to come, which is passed to 'getTracks'. -}-parse :: Parser.C parser => parser (PossiblyIncomplete MIDIFile.T)+parse :: Parser.C parser => Parser.Partial (Parser.Fallible parser) MIDIFile.T parse = getChunk >>= \ (typ, hdLen) -> case typ of "MThd" -> do (format, nTracks, division) <-- ParserRestricted.run hdLen getHeader- ~(me, tracks) <-- Parser.zeroOrMoreInc- (do (me0,track) <- getTrackChunk-{-- let trackNoEOT = track--}- trackNoEOT <-- maybe (return Nothing) (liftM Just . removeEndOfTrack) track- return (me0, trackNoEOT))- let n = genericLength tracks- Parser.force $ when (n /= nTracks) $- Parser.warn ("header says " ++ show nTracks ++- " tracks, but " ++ show n ++ " tracks were found")- return (me, MIDIFile.Cons format division $ catMaybes tracks)- _ -> Parser.warn ("found Alien chunk <" ++ typ ++ ">") >>+ RestrictedParser.runFallible hdLen getHeader+ excTracks <-+ lift $ Parser.zeroOrMoreInc+ (getTrackChunk >>= Async.mapM (lift . liftMaybe removeEndOfTrack))+ flip Async.mapM excTracks $ \tracks ->+ do let n = genericLength tracks+ lift $ Parser.warnIf (n /= nTracks)+ ("header says " ++ show nTracks +++ " tracks, but " ++ show n ++ " tracks were found")+ return (MIDIFile.Cons format division $ catMaybes tracks)+ _ -> lift (Parser.warn ("found Alien chunk <" ++ typ ++ ">")) >> Parser.skip hdLen >> parse +liftMaybe :: Monad m => (a -> m b) -> Maybe a -> m (Maybe b)+liftMaybe f = maybe (return Nothing) (liftM Just . f)+ {- | There are two ways to mark the end of the track: The end of the event list and the meta event 'EndOfTrack'.@@ -143,12 +141,12 @@ (\(initEvents, lastEvent) -> let (eots, track) = EventList.partition isEndOfTrack initEvents- in do Parser.force $ when+ in do Parser.warnIf (not $ EventList.null eots)- (Parser.warn "EndOfTrack inside a track")- Parser.force $ when+ "EndOfTrack inside a track"+ Parser.warnIf (not $ isEndOfTrack $ snd lastEvent)- (Parser.warn "Track does not end with EndOfTrack")+ "Track does not end with EndOfTrack" return track) (EventList.viewR xs) @@ -180,23 +178,23 @@ four bytes for the size of the coming data, and the data itself. -}-getChunk :: Parser.C parser => parser (String, NonNeg.Integer)+getChunk :: Parser.C parser => Parser.Fallible parser (String, NonNeg.Integer) getChunk = liftM2 (,) (getString 4) -- chunk type: header or track (getNByteCardinal 4) -- chunk body -getTrackChunk :: Parser.C parser => parser (PossiblyIncomplete (Maybe Track))+getTrackChunk :: Parser.C parser => Parser.Partial (Parser.Fallible parser) (Maybe Track) getTrackChunk = do (typ, len) <- getChunk if typ=="MTrk"- then liftM (mapSnd Just) $- ParserRestricted.run len $+ then liftM (fmap Just) $ lift $+ RestrictedParser.run len $ StatusParser.run getTrack- else Parser.warn ("found Alien chunk <" ++ typ ++ "> in track section") >>+ else lift (Parser.warn ("found Alien chunk <" ++ typ ++ "> in track section")) >> Parser.skip len >>- return (Nothing, Nothing)+ return (Async.pure Nothing) @@ -205,7 +203,7 @@ the number of track chunks to come, and the smallest time division to be used in reading the rest of the file. -}-getHeader :: Parser.C parser => parser (MIDIFile.Type, NonNeg.Int, Division)+getHeader :: Parser.C parser => Parser.Fallible parser (MIDIFile.Type, NonNeg.Int, Division) getHeader = do format <- makeEnum =<< get2@@ -217,7 +215,7 @@ The division is implemented thus: the most significant bit is 0 if it's in ticks per quarter note; 1 if it's an SMPTE value. -}-getDivision :: Parser.C parser => parser Division+getDivision :: Parser.C parser => Parser.Fallible parser Division getDivision = do x <- get1@@ -231,11 +229,11 @@ A track is a series of events. Parse a track, stopping when the size is zero. -}-getTrack :: Parser.C parser => StatusParser.T parser (PossiblyIncomplete MIDIFile.Track)+getTrack :: Parser.C parser => Parser.Partial (StatusParser.T parser) MIDIFile.Track getTrack = liftM- (mapSnd EventList.fromPairList)- (ParserState.zeroOrMore Event.getTrackEvent)+ (fmap EventList.fromPairList)+ (Parser.zeroOrMore Event.getTrackEvent) @@ -251,7 +249,7 @@ showChunks :: ByteList -> String showChunks mf =- showMR getChunks (\(me,cs) ->+ showMR (lift getChunks) (\(Async.Exceptional me cs) -> unlinesS (map pp cs) . maybe id (\e -> showString ("incomplete chunk list: " ++ e ++ "\n")) me) mf "" where@@ -261,8 +259,8 @@ showMR getHeader shows contents pp ("MTrk",contents) = showString "Track:\n" .- showMR (StatusParser.run getTrack)- (\(me,track) str ->+ showMR (lift $ StatusParser.run getTrack)+ (\(Async.Exceptional me track) str -> EventList.foldr MIDIFile.showTime (\e -> MIDIFile.showEvent e . showString "\n")@@ -276,9 +274,9 @@ showString "\n" -showMR :: ParserStream.T ParserStream.ByteList a -> (a->ShowS) -> ByteList -> ShowS+showMR :: Parser.Fallible (StreamParser.T StreamParser.ByteList) a -> (a->ShowS) -> ByteList -> ShowS showMR m pp contents =- let report = ParserStream.run m (ParserStream.ByteList contents)+ let report = StreamParser.run m (StreamParser.ByteList contents) in unlinesS (map showString $ Report.warnings report) . either showString pp (Report.result report) @@ -297,7 +295,7 @@ -} getChunks ::- Parser.C parser => parser (PossiblyIncomplete [(String, ByteList)])+ Parser.C parser => Parser.Partial parser [(String, ByteList)] getChunks = Parser.zeroOrMore $ do (typ, len) <- getChunk
src/Sound/MIDI/Manufacturer.hs view
@@ -349,7 +349,7 @@ -- * serialization -get :: Parser.C parser => parser T+get :: Parser.C parser => Parser.Fallible parser T get = do subId <- getByte if subId == 0
src/Sound/MIDI/Message.hs view
@@ -14,8 +14,7 @@ import qualified Sound.MIDI.Message.System as System import qualified Sound.MIDI.Parser.Status as StatusParser-import Sound.MIDI.Parser.Class (PossiblyIncomplete, )-import qualified Sound.MIDI.Parser.Class as Parser+import qualified Sound.MIDI.Parser.Class as Parser import Sound.MIDI.Parser.Primitive (get1) import qualified Sound.MIDI.Parser.ByteString as ParserByteString @@ -24,20 +23,20 @@ import qualified Sound.MIDI.Parser.Report as Report +import qualified Control.Monad.Exception.Asynchronous as Async+ import Control.Monad.Trans (lift, ) import Control.Monad (liftM, ) import qualified Data.ByteString.Lazy as B -import Sound.MIDI.Utility (mapSnd, ) - data T = Channel Channel.T | System System.T -get :: Parser.C parser => parser T+get :: Parser.C parser => Parser.Fallible parser T get = get1 >>= \code -> if code >= 0xF0@@ -45,7 +44,7 @@ else liftM Channel $ (uncurry Channel.get (Channel.decodeStatus code) =<< get1) -- else liftM Channel $ StatusParser.run (Channel.getWithStatus code) -getWithStatus :: Parser.C parser => StatusParser.T parser T+getWithStatus :: Parser.C parser => Parser.Fallible (StatusParser.T parser) T getWithStatus = StatusParser.lift get1 >>= \code -> if code >= 0xF0@@ -54,12 +53,12 @@ else liftM Channel $ Channel.getWithStatus code getIncompleteWithStatus ::- Parser.C parser => StatusParser.T parser (PossiblyIncomplete T)+ Parser.C parser => Parser.Partial (Parser.Fallible (StatusParser.T parser)) T getIncompleteWithStatus = StatusParser.lift get1 >>= \code -> if code >= 0xF0- then liftM (mapSnd System) $ StatusParser.lift $ System.getIncomplete code- else liftM ((,) Nothing . Channel) $ Channel.getWithStatus code+ then liftM (fmap System) $ StatusParser.lift $ System.getIncomplete code+ else liftM (Async.pure . Channel) $ Channel.getWithStatus code maybeFromByteString :: B.ByteString -> Report.T T maybeFromByteString =
src/Sound/MIDI/Message/Channel.hs view
@@ -20,9 +20,9 @@ import Sound.MIDI.Parser.Primitive import qualified Sound.MIDI.Parser.Class as Parser import Sound.MIDI.Parser.Status- (lift,- Channel, fromChannel, toChannel, )+ (Channel, fromChannel, toChannel, ) +import Control.Monad.Trans (lift, ) import Control.Monad (liftM, liftM2, when, ) import qualified Sound.MIDI.Writer.Status as StatusWriter@@ -74,17 +74,17 @@ (it's been nice enough to keep track of it for us), and the tag that we've already gotten is the first byte of data. -}-getWithStatus :: Parser.C parser => Int -> StatusParser.T parser T+getWithStatus :: Parser.C parser => Int -> Parser.Fallible (StatusParser.T parser) T getWithStatus tag = do (status@(code, channel), firstData) <- if tag < 0x80 then maybe- (lift $ Parser.giveUp "messages wants to repeat status byte, but there was no status yet")+ (Parser.giveUp "messages wants to repeat status byte, but there was no status yet") (\cc -> return (cc,tag)) =<< StatusParser.get- else liftM ((,) $ decodeStatus tag) $ lift get1+ else liftM ((,) $ decodeStatus tag) $ StatusParser.lift get1 StatusParser.set (Just status)- lift $ get code channel firstData+ StatusParser.lift $ get code channel firstData -- | for internal use decodeStatus :: Int -> (Int, Channel)@@ -97,7 +97,7 @@ the code, channel and first data byte must be determined by the caller. -}-get :: Parser.C parser => Int -> Channel -> Int -> parser T+get :: Parser.C parser => Int -> Channel -> Int -> Parser.Fallible parser T get code channel firstData = liftM (Cons channel) $ if code == 11 && firstData >= 0x78
src/Sound/MIDI/Message/Channel/Mode.hs view
@@ -12,12 +12,14 @@ import Sound.MIDI.Parser.Report (UserMessage, ) +import qualified Control.Monad.Exception.Asynchronous as Async import Sound.MIDI.Utility (toMaybe, )+import Control.Monad.Trans (lift, )+import Control.Monad (liftM, ) import Test.QuickCheck (Arbitrary(arbitrary), ) import qualified Test.QuickCheck as QC -import Control.Monad (liftM, ) data T =@@ -46,13 +48,15 @@ -- * serialization -get :: Parser.C parser => Int -> parser T+get :: Parser.C parser => Int -> Parser.Fallible parser T get mode = do x <- get1- let (w, msg) = fromControllerValue (mode,x)- maybe (return ()) Parser.warn w- return msg+ lift $ Parser.warnIncomplete $ uncurry Async.Exceptional $ fromControllerValue (mode,x) +{- |+This function is also used in alsa-midi,+we could give it the result type @Parser.PossiblyIncomplete T@ otherwise.+-} fromControllerValue :: Integral a => (a, a) -> (Maybe UserMessage, T) fromControllerValue (mode,x) = case mode of
src/Sound/MIDI/Message/Channel/Voice.hs view
@@ -409,7 +409,7 @@ -- * serialization -get :: Parser.C parser => Int -> Int -> parser T+get :: Parser.C parser => Int -> Int -> Parser.Fallible parser T get code firstData = let pitch = toPitch firstData getVel = liftM toVelocity get1
src/Sound/MIDI/Message/System.hs view
@@ -10,14 +10,14 @@ import qualified Sound.MIDI.Message.System.RealTime as RealTime -- import Sound.MIDI.Parser.Primitive-import Sound.MIDI.Parser.Class (PossiblyIncomplete, ) import qualified Sound.MIDI.Parser.Class as Parser import qualified Sound.MIDI.Writer.Basic as Writer +import qualified Control.Monad.Exception.Asynchronous as Async+ import Control.Monad (liftM, ) -import Sound.MIDI.Utility (mapSnd, ) data T =@@ -26,7 +26,7 @@ | RealTime RealTime.T -get :: Parser.C parser => Int -> parser T+get :: Parser.C parser => Int -> Parser.Fallible parser T get code = if code == 0xF0 then liftM Exclusive Exclusive.get@@ -38,16 +38,16 @@ then liftM RealTime $ RealTime.get code else Parser.giveUp ("invalid System message code " ++ show code) -getIncomplete :: Parser.C parser => Int -> parser (PossiblyIncomplete T)+getIncomplete :: Parser.C parser => Int -> Parser.Partial (Parser.Fallible parser) T getIncomplete code = if code == 0xF0- then liftM (mapSnd Exclusive) Exclusive.getIncomplete+ then liftM (fmap Exclusive) Exclusive.getIncomplete else if code >= 0xF1 && code <= 0xF6- then liftM ((,) Nothing . Common) $ Common.get code+ then liftM (Async.pure . Common) $ Common.get code else if code >= 0xF8 && code <= 0xFF- then liftM ((,) Nothing . RealTime) $ RealTime.get code+ then liftM (Async.pure . RealTime) $ RealTime.get code else Parser.giveUp ("invalid System message code " ++ show code)
src/Sound/MIDI/Message/System/Common.hs view
@@ -8,7 +8,8 @@ import Sound.MIDI.Parser.Primitive import qualified Sound.MIDI.Parser.Class as Parser -import Control.Monad (liftM, liftM2, when, )+import Control.Monad.Trans (lift, )+import Control.Monad (liftM, liftM2, ) import qualified Sound.MIDI.Writer.Basic as Writer @@ -38,15 +39,15 @@ -- * serialization -get :: Parser.C parser => Int -> parser T+get :: Parser.C parser => Int -> Parser.Fallible parser T get code = case code of 0xF1 -> do dat <- get1 let (nib, value) = Bit.splitAt 4 dat let (msb, nibble) = Bit.splitAt 3 nib- when (msb/=0)- (Parser.warn "TimeCodeQuarterFrame: most significant bit must 0")+ lift $ Parser.warnIf (msb/=0)+ "TimeCodeQuarterFrame: most significant bit must 0" return $ TimeCodeQuarterFrame (toEnum nibble) value 0xF2 -> liftM2 (\lsb msb -> SongPositionPointer (lsb + Bit.shiftL 7 msb)) get1 get1 0xF3 -> liftM SongSelect get1
src/Sound/MIDI/Message/System/Exclusive.hs view
@@ -9,12 +9,12 @@ import Sound.MIDI.IO (ByteList) import Sound.MIDI.Parser.Primitive-import Sound.MIDI.Parser.Class (PossiblyIncomplete, ) import qualified Sound.MIDI.Parser.Class as Parser import qualified Sound.MIDI.Writer.Basic as Writer --- import Control.Monad (liftM, liftM2, when, )+import Control.Monad.Trans (MonadTrans, lift, )+import qualified Control.Monad.Exception.Asynchronous as Async import Data.Maybe (fromMaybe, ) @@ -39,22 +39,26 @@ -- * serialization -get :: Parser.C parser => parser T+get :: Parser.C parser => Parser.Fallible parser T get =- do (err, sysex) <- getIncomplete+ do (Async.Exceptional err sysex) <- getIncomplete maybe (return sysex) Parser.giveUp err -getIncomplete :: Parser.C parser => parser (PossiblyIncomplete T)+getIncomplete :: Parser.C parser => Parser.Partial (Parser.Fallible parser) T getIncomplete = do manu <- Manufacturer.get- (err, body) <- Parser.until (0xf7 ==) getByte- return $ ((,) err) $- fromMaybe (Commercial manu body) $- lookup manu $- (Manufacturer.nonCommercial, NonCommercial body) :- (Manufacturer.nonRealTime, NonRealTime $ NonRealTimeCons body) :- (Manufacturer.realTime, RealTime $ RealTimeCons body) :- []+ incBody <- lift getBody+ return $ flip fmap incBody $ \body ->+ fromMaybe (Commercial manu body) $+ lookup manu $+ (Manufacturer.nonCommercial, NonCommercial body) :+ (Manufacturer.nonRealTime, NonRealTime $ NonRealTimeCons body) :+ (Manufacturer.realTime, RealTime $ RealTimeCons body) :+ []++getBody :: Parser.C parser => Parser.Partial parser ByteList+getBody = Parser.until (0xf7 ==) getByte+ {- | It is not checked whether SysEx messages contain only 7-bit values.
src/Sound/MIDI/Message/System/RealTime.hs view
@@ -25,7 +25,7 @@ -- * serialization -get :: Parser.C parser => Int -> parser T+get :: Parser.C parser => Int -> Parser.Fallible parser T get code = case code of 0xF8 -> return TimingClock
src/Sound/MIDI/Parser/ByteString.hs view
@@ -10,12 +10,17 @@ import qualified Data.Binary.Get as Binary import Data.Binary.Get (Get, runGet, ) -import Control.Monad (liftM, when, )+import Control.Monad.Trans (lift, ) +import qualified Sound.MIDI.Parser.Report as Report+ import qualified Sound.MIDI.Parser.Class as Parser import Sound.MIDI.Parser.Class (UserMessage, PossiblyIncomplete, )-import qualified Sound.MIDI.Parser.Report as Report+import qualified Sound.MIDI.Parser.Exception as Exception+import qualified Sound.MIDI.Parser.Warning as Warning +-- import qualified Control.Monad.Exception.Synchronous as Sync+ -- import Data.Word (Word8) import Data.Int (Int64)@@ -25,7 +30,7 @@ -newtype T a = Cons {decons :: Get (Report.T a)}+newtype T a = Cons {decons :: Warning.T Get a} {-@@ -35,12 +40,13 @@ -} -run :: T a -> B.ByteString -> Report.T a+run :: Parser.Fallible T a -> B.ByteString -> Report.T a run parser input =- flip runGet input $ decons $+ flip runGet input $ Warning.run $ decons $ Exception.run $ (do a <- parser- end <- Parser.isEnd- when (not end) (warn "unparsed data left over")+ lift $+ Parser.isEnd >>= \end ->+ Parser.warnIf (not end) "unparsed data left over" return a) {- |@@ -49,50 +55,44 @@ and thus further parsing is not possible. -} runIncomplete ::- T (PossiblyIncomplete a) -> B.ByteString -> Report.T a+ Parser.Partial (Parser.Fallible T) a -> B.ByteString -> Report.T a runIncomplete parser input = flip run input $- do (me,x) <- parser- maybe (return ()) warn me- return x+ lift . Parser.warnIncomplete =<< parser fromGet :: Get a -> T a fromGet p =- Cons $ liftM (\a -> Report.Cons [] (Right a)) p+ Cons $ lift p instance Monad T where- return x = fromGet $ return x- x >>= y = Cons $- decons x >>= \ a ->- case Report.result a of- Left err -> return (Report.Cons (Report.warnings a) (Left err))- Right ar ->- decons (y ar) >>= \ b ->- return (b{Report.warnings = Report.warnings a ++ Report.warnings b})+ return = Cons . return+ x >>= y = Cons $ decons . y =<< decons x ++instance Parser.EndCheck T where+ isEnd = fromGet Binary.isEmpty+ instance Parser.C T where- isEnd = fromGet Binary.isEmpty -- getByte = fromGet Binary.getWord8--- a get getMabybeWord8 would be nice in order to avoid double-checking+-- a get getMaybeWord8 would be nice in order to avoid double-checking getByte =- do end <- fromGet Binary.isEmpty+ do end <- lift $ fromGet Binary.isEmpty if end- then giveUp "unexpected end of ByteString"- else fromGet Binary.getWord8- skip n =+ then Parser.giveUp "unexpected end of ByteString"+ else lift $ fromGet Binary.getWord8++ skip n = let toSize x = let y = if x > fromIntegral (maxBound `asTypeOf` y) then error "skip: number too big" else fromIntegral x in y- in fromGet $ skip $ toSize $ NonNeg.toNumber n- warn = warn- giveUp = giveUp- try = try- force = force+ in lift $ fromGet $ skip $ toSize $ NonNeg.toNumber n + warn = Cons . Warning.warn+ {- | In contrast to Binary.skip this one does not fail badly and it works with Int64. I hope that it is not too inefficient.@@ -100,30 +100,3 @@ skip :: Int64 -> Get () skip n = Binary.getLazyByteString n >> return () -- Binary.skip n--warn :: String -> T ()-warn text =- Cons $ return $ Report.Cons [text] (Right ())--giveUp :: String -> T a-giveUp text =- Cons $ return $ Report.Cons [] (Left text)--try :: T a -> T (Either UserMessage a)-try =- Cons . liftM (\r -> r{Report.result = Right (Report.result r)}) . decons---{- |-Wadler's force function--'force' guarantees that the parser does not fail.-Thus it makes parsing more lazy.-However if the original parser fails though,-then we get an unrecoverable /irrefutable pattern/ error on 'Just'.--}-force :: T a -> T a-force p =- Cons $- do ~(Report.Cons w ~(Right x)) <- decons p- return (Report.Cons w (Right x))
src/Sound/MIDI/Parser/Class.hs view
@@ -1,39 +1,47 @@ module Sound.MIDI.Parser.Class- (C, isEnd, getByte,- warn, giveUp, try,- force, zeroOrMore, zeroOrMoreInc, until, replicate, skip,- PossiblyIncomplete, UserMessage,+ (EndCheck, isEnd,+ C, getByte, skip,+ warn, warnIf, warnIncomplete, Exc.giveUp, Exc.try,+ until, zeroOrMore, zeroOrMoreInc, replicate,+ emptyList, PossiblyIncomplete, UserMessage,+ Fallible, Partial, {- for debugging- handleMsg, appendIncomplete,+ absorbException, appendIncomplete, -} ) where import Sound.MIDI.Parser.Report (UserMessage)+import qualified Sound.MIDI.Parser.Exception as Exc+import qualified Control.Monad.Exception.Asynchronous as Async+import qualified Control.Monad.Exception.Synchronous as Sync -import Control.Monad (liftM, liftM2, )+import Control.Monad.Trans (lift, )+import Control.Monad.State (StateT, )+import Control.Monad (liftM, liftM2, when, ) import Data.Word (Word8) import qualified Numeric.NonNegative.Wrapper as NonNeg -import Sound.MIDI.Utility (mapSnd, )- import Prelude hiding (replicate, until, ) -class Monad parser => C parser where+class Monad parser => EndCheck parser where isEnd :: parser Bool- getByte :: parser Word8- skip :: NonNeg.Integer -> parser ()- warn :: UserMessage -> parser ()- giveUp :: UserMessage -> parser a- try :: parser a -> parser (Either UserMessage a)- force :: parser a -> parser a +-- would be probably better placed in Parser.Status+instance EndCheck parser => EndCheck (StateT st parser) where+ isEnd = lift $ isEnd +class EndCheck parser => C parser where+ getByte :: Fallible parser Word8+ skip :: NonNeg.Integer -> Fallible parser ()+ warn :: UserMessage -> parser ()++ {- | @PossiblyIncomplete@ represents a value like a list that can be the result of an incomplete parse.@@ -41,51 +49,60 @@ It is not possible to merge this functionality in the parser monad, because then it is not possible to define monadic binding.--In the future it should be replaced by-'Control.Monad.Exception.Asynchronous.Exceptional'-from the explicit-exception package. -}-type PossiblyIncomplete a = (Maybe UserMessage, a)+type PossiblyIncomplete a = Async.Exceptional UserMessage a +type Fallible parser = Sync.ExceptionalT UserMessage parser+type Partial parser a = parser (PossiblyIncomplete a) -{--zeroOrMore :: T s a -> T s [a]-zeroOrMore p = force $ oneOrMore p `mplus` return [] -oneOrMore :: T s a -> T s [a]-oneOrMore p = liftM2 (:) p (zeroOrMore p)+warnIf :: C parser => Bool -> UserMessage -> parser ()+warnIf b msg = when b (warn msg)++{- |+Emit a warning if a value is said to be incomplete.+Be careful using this function,+because an incomplete value often means+that subsequent parse actions will process data from the wrong position.+Only use this function if you+either know that the parse is complete also if the parsed value is incomplete+or if there are no subsequent parse actions to run.++This function cannot fail. -}+warnIncomplete :: C parser => PossiblyIncomplete a -> parser a+warnIncomplete ~(Async.Exceptional me a) =+ do maybe (return ()) warn me+ return a + {- | This function will never fail. If the element parser fails somewhere, a prefix of the complete list is returned along with the error message. -}-zeroOrMore :: C parser =>- parser a -> parser (PossiblyIncomplete [a])+zeroOrMore :: EndCheck parser =>+ Fallible parser a -> Partial parser [a] zeroOrMore p = let go =- force $ isEnd >>= \b ->+ isEnd >>= \b -> if b- then return (Nothing, [])- else handleMsg- (\errMsg -> (Just errMsg, []))- (liftM2 (\ x ~(e,xs) -> (e,x:xs)) p go)+ then return emptyList+ else absorbException+ (liftM2 (\ x -> fmap (x:)) p (lift go)) in go -zeroOrMoreInc :: C parser =>- parser (PossiblyIncomplete a) -> parser (PossiblyIncomplete [a])+zeroOrMoreInc :: EndCheck parser =>+ Partial (Fallible parser) a -> Partial parser [a] zeroOrMoreInc p = let go =- force $ isEnd >>= \b ->+ isEnd >>= \b -> if b- then return (Nothing, [])- else handleMsg- (\errMsg -> (Just errMsg, []))+ then return emptyList+ else absorbException (appendIncomplete p go) in go @@ -95,23 +112,23 @@ The terminating element is consumed by the parser but not appended to the result list. If the end of the input is reached without finding the terminating element,-then an Incomplete exception (Just errorMessage) is signalled.+then an Incomplete exception (Just errorMessage) is signaled. -}-until :: C parser =>- (a -> Bool) -> parser a -> parser (PossiblyIncomplete [a])+until :: EndCheck parser =>+ (a -> Bool) -> Fallible parser a -> Partial parser [a] until c p = let go =- force $ isEnd >>= \b ->+ isEnd >>= \b -> if b then- return (Just "Parser.until: unexpected end of input", [])+ return $ Async.broken+ "Parser.until: unexpected end of input" [] else- handleMsg- (\errMsg -> (Just errMsg, [])) $+ absorbException $ p >>= \x -> if c x- then return (Nothing, [])- else liftM (mapSnd (x:)) go+ then return emptyList+ else liftM (fmap (x:)) (lift go) in go @@ -122,35 +139,36 @@ replicate :: C parser => NonNeg.Int ->- parser (PossiblyIncomplete a) ->- parser (PossiblyIncomplete [a])+ Partial (Fallible parser) a ->+ Partial parser [a] replicate m p = let go n =- force $- if n==0- then return (Nothing, [])- else handleMsg- (\errMsg -> (Just errMsg, []))- (appendIncomplete p (go (n-1)))+ if n==0+ then return emptyList+ else absorbException+ (appendIncomplete p (go (n-1))) in go m ++emptyList :: PossiblyIncomplete [a]+emptyList = Async.pure []+ {- | The first parser may fail, but the second one must not. -} appendIncomplete ::- C parser =>- parser (PossiblyIncomplete a) ->- parser (PossiblyIncomplete [a]) ->- parser (PossiblyIncomplete [a])+ Monad parser =>+ Partial (Fallible parser) a ->+ Partial parser [a] ->+ Partial (Fallible parser) [a] appendIncomplete p ps =- do ~(me, x) <- p- liftM (mapSnd (x:)) $ force $- maybe ps (\_ -> return (me,[])) me+ do ~(Async.Exceptional me x) <- p+ lift $ liftM (fmap (x:)) $+ maybe ps (\_ -> return (Async.Exceptional me [])) me -handleMsg ::- C parser =>- (UserMessage -> a) -> parser a -> parser a-handleMsg handler action =- liftM- (either handler id)- (try action)+absorbException ::+ Monad parser =>+ Partial (Fallible parser) [a] ->+ Partial parser [a]+absorbException =+ Sync.resolveT (\errMsg -> return $ Async.broken errMsg [])
+ src/Sound/MIDI/Parser/Exception.hs view
@@ -0,0 +1,24 @@+{- |+Handling of exceptions.+-}+module Sound.MIDI.Parser.Exception where++import qualified Sound.MIDI.Parser.Report as Report+-- import qualified Sound.MIDI.Parser.Warning as Warning++import qualified Control.Monad.Exception.Synchronous as Sync+++type T m = Sync.ExceptionalT Report.UserMessage m+++run :: Monad m =>+ T m a -> m (Sync.Exceptional Report.UserMessage a)+run = Sync.runExceptionalT+++giveUp :: Monad m => String -> T m a+giveUp = Sync.throwT++try :: Monad m => T m a -> m (Sync.Exceptional Report.UserMessage a)+try = Sync.tryT
src/Sound/MIDI/Parser/File.hs view
@@ -5,10 +5,12 @@ import qualified Sound.MIDI.Parser.Class as Parser import Sound.MIDI.Parser.Class (UserMessage, PossiblyIncomplete, ) -import Control.Monad.Reader (ReaderT(ReaderT, runReaderT), ask, liftM, lift, )+import Control.Monad.Reader (ReaderT(runReaderT), ask, liftM, lift, ) import qualified System.IO.Error as IOE import qualified Control.Exception as Exc+import qualified Control.Monad.Exception.Asynchronous as Async+import qualified Control.Monad.Exception.Synchronous as Sync import qualified System.IO as IO import qualified Sound.MIDI.IO as MIO@@ -21,16 +23,17 @@ newtype T a = Cons {decons :: ReaderT IO.Handle IO a} -runFile :: T a -> FilePath -> IO a+runFile :: Parser.Fallible T a -> FilePath -> IO a runFile p name = Exc.bracket (IO.openBinaryFile name IO.ReadMode) IO.hClose (runHandle p) -runHandle :: T a -> IO.Handle -> IO a+runHandle :: Parser.Fallible T a -> IO.Handle -> IO a runHandle p h =- runReaderT (decons p) h+ do exc <- runReaderT (decons (Sync.tryT p)) h+ Sync.resolve (IOE.ioError . IOE.userError) (fmap return exc) @@ -39,13 +42,13 @@ we cannot know where the current file position is, we omit the @runIncompleteHandle@ variant. -}-runIncompleteFile :: T (PossiblyIncomplete a) -> FilePath -> IO a+runIncompleteFile :: Parser.Partial (Parser.Fallible T) a -> FilePath -> IO a runIncompleteFile p name = Exc.bracket (IO.openBinaryFile name IO.ReadMode) IO.hClose (\h ->- do (me,a) <- runHandle p h+ do (Async.Exceptional me a) <- runHandle p h maybe (return ()) (\msg -> putStrLn $ "could not parse MIDI file completely: " ++ msg) me return a)@@ -59,14 +62,16 @@ fromIO :: (IO.Handle -> IO a) -> T a fromIO act = Cons $ lift . act =<< ask -instance Parser.C T where+fallibleFromIO :: (IO.Handle -> IO a) -> Parser.Fallible T a+fallibleFromIO act =+ Sync.ExceptionalT . Cons . lift .+ fmap (Sync.mapException show . Sync.fromEither) . IOE.try . act+ =<< lift (Cons ask)++instance Parser.EndCheck T where isEnd = fromIO IO.hIsEOF- getByte = fromIO $ liftM (fromIntegral . ord) . IO.hGetChar- skip n = fromIO $ \h -> IO.hSeek h IO.RelativeSeek (NonNeg.toNumber n)++instance Parser.C T where+ getByte = fallibleFromIO $ liftM (fromIntegral . ord) . IO.hGetChar+ skip n = fallibleFromIO $ \h -> IO.hSeek h IO.RelativeSeek (NonNeg.toNumber n) warn = Cons . lift . (\msg -> putStrLn ("warning: " ++ msg))- giveUp = Cons . lift . IOE.ioError . IOE.userError- try p =- Cons $ ReaderT $ \h ->- liftM (either (Left . show) Right) $- IOE.try $ runReaderT (decons p) h- force p = p
src/Sound/MIDI/Parser/Primitive.hs view
@@ -24,20 +24,20 @@ {- | 'getByte' gets a single byte from the input. -}-getByte :: Parser.C parser => parser Word8+getByte :: Parser.C parser => Parser.Fallible parser Word8 getByte = Parser.getByte {- | @getN n@ returns n characters (bytes) from the input. -}-getN :: Parser.C parser => NonNeg.Int -> parser ByteList+getN :: Parser.C parser => NonNeg.Int -> Parser.Fallible parser ByteList getN n = replicateM (NonNeg.toNumber n) getByte -getString :: Parser.C parser => NonNeg.Integer -> parser String+getString :: Parser.C parser => NonNeg.Integer -> Parser.Fallible parser String getString n = liftM listCharFromByte (getBigN n) -getBigN :: Parser.C parser => NonNeg.Integer -> parser ByteList+getBigN :: Parser.C parser => NonNeg.Integer -> Parser.Fallible parser ByteList getBigN n = sequence $ Bit.replicateBig@@ -51,22 +51,22 @@ 4-byte numbers from the input (respectively), convert the base-256 data into a single number, and return. -}-get1 :: Parser.C parser => parser Int+get1 :: Parser.C parser => Parser.Fallible parser Int get1 = liftM fromIntegral getByte -getNByteInt :: Parser.C parser => NonNeg.Int -> parser Int+getNByteInt :: Parser.C parser => NonNeg.Int -> Parser.Fallible parser Int getNByteInt n = liftM Bit.fromBytes (replicateM (NonNeg.toNumber n) get1) -get2, get3, get4 :: Parser.C parser => parser Int+get2, get3, get4 :: Parser.C parser => Parser.Fallible parser Int get2 = getNByteInt 2 get3 = getNByteInt 3 get4 = getNByteInt 4 -getByteAsCardinal :: Parser.C parser => parser NonNeg.Integer+getByteAsCardinal :: Parser.C parser => Parser.Fallible parser NonNeg.Integer getByteAsCardinal = liftM fromIntegral getByte -getNByteCardinal :: Parser.C parser => NonNeg.Int -> parser NonNeg.Integer+getNByteCardinal :: Parser.C parser => NonNeg.Int -> Parser.Fallible parser NonNeg.Integer getNByteCardinal n = liftM Bit.fromBytes (replicateM (NonNeg.toNumber n) getByteAsCardinal) @@ -79,7 +79,7 @@ If it's @0@, that byte is the last one. 'getVar' gets a variable-length quantity from the input. -}-getVar :: Parser.C parser => parser NonNeg.Integer+getVar :: Parser.C parser => Parser.Fallible parser NonNeg.Integer getVar = liftM (Bit.fromBase (2^(7::Int)) . map fromIntegral) getVarBytes @@ -87,7 +87,7 @@ The returned list contains only bytes with the most significant bit cleared. These are digits of a 128-ary number. -}-getVarBytes :: Parser.C parser => parser [Word8]+getVarBytes :: Parser.C parser => Parser.Fallible parser [Word8] getVarBytes = do digit <- getByte@@ -96,12 +96,12 @@ else return [digit] -getEnum :: (Parser.C parser, Enum enum, Bounded enum) => parser enum+getEnum :: (Parser.C parser, Enum enum, Bounded enum) => Parser.Fallible parser enum getEnum = makeEnum =<< get1 -makeEnum :: (Parser.C parser, Enum enum, Bounded enum) => Int -> parser enum+makeEnum :: (Parser.C parser, Enum enum, Bounded enum) => Int -> Parser.Fallible parser enum makeEnum n =- let go :: (Parser.C parser, Enum a) => a -> a -> parser a+ let go :: (Parser.C parser, Enum a) => a -> a -> Parser.Fallible parser a go lower upper = if fromEnum lower <= n && n <= fromEnum upper then return (toEnum n)
src/Sound/MIDI/Parser/Restricted.hs view
@@ -4,13 +4,14 @@ where the length of a part is fixed by a length specification. -} module Sound.MIDI.Parser.Restricted- (T(..), run, ) where+ (T(..), run, runFallible, ) where import qualified Sound.MIDI.Parser.Class as Parser -import Control.Monad.State- (StateT(StateT, runStateT), mapStateT,- get, put, liftM, lift, when, )+import qualified Control.Monad.Exception.Synchronous as Sync+import qualified Control.Monad.Trans as Trans+import qualified Control.Monad.State as State+import Control.Monad.State (StateT(runStateT), gets, get, put, when, ) import qualified Numeric.NonNegative.Wrapper as NonNeg @@ -21,13 +22,21 @@ NonNeg.Integer -> T parser a -> parser a run maxLen p = do (x,remaining) <- runStateT (decons p) maxLen- Parser.force $ when+ Parser.warnIf (remaining>0)- (Parser.warn ("unparsed bytes left in part (" ++ show remaining ++ " bytes)"))+ ("unparsed bytes left in part (" ++ show remaining ++ " bytes)") return x +runFallible :: Parser.C parser =>+ NonNeg.Integer -> Parser.Fallible (T parser) a -> Parser.Fallible parser a+runFallible len = Sync.mapExceptionalT (run len) +lift :: Monad parser => Parser.Fallible parser a -> Parser.Fallible (T parser) a+lift = Sync.mapExceptionalT State.lift+++ newtype T parser a = Cons {decons :: StateT NonNeg.Integer parser a} @@ -35,12 +44,28 @@ return = Cons . return x >>= y = Cons $ decons . y =<< decons x -instance Parser.C parser => Parser.C (T parser) where+instance Trans.MonadTrans T where+ lift = Cons . State.lift+++getRemaining :: Monad parser => Parser.Fallible (T parser) NonNeg.Integer+getRemaining = Trans.lift $ Cons get++putRemaining :: Monad parser => NonNeg.Integer -> Parser.Fallible (T parser) ()+putRemaining = Trans.lift . Cons . put+++instance Parser.EndCheck parser => Parser.EndCheck (T parser) where isEnd =- Cons $ get >>= \remaining ->- if remaining==0 then return True else lift Parser.isEnd+ {- if remaining>0 then we do not check+ whether there are actually more bytes in the stream+ because that will be catched anyway on the next getByte or skip -}+ Cons (gets (0==))+-- if remaining==0 then return True else lift Parser.isEnd++instance Parser.C parser => Parser.C (T parser) where getByte =- Cons $ get >>= \remaining ->+ getRemaining >>= \remaining -> do when (remaining==0) (lift $ Parser.giveUp "unexpected end of part") {- in principle not necessary, because Parser.getByte must check for remaining bytes@@ -48,23 +73,14 @@ when end (lift $ Parser.giveUp "part longer than container") -}- put (remaining-1)+ putRemaining (remaining-1) lift Parser.getByte+ skip n =- Cons $ get >>= \remaining ->+ getRemaining >>= \remaining -> if n>remaining then lift $ Parser.giveUp "skip beyond end of part"- else put (remaining-n) >> lift (Parser.skip n)- warn = Cons . lift . Parser.warn- -- giveUp = Cons . lift . giveUp- giveUp errMsg =- Cons $ StateT $ \remain ->- Parser.skip remain >> Parser.giveUp errMsg- try (Cons st) =- Cons $ StateT $ \remain0 ->- liftM (either- (\errMsg -> (Left errMsg, 0))- (\(x,remain1) -> (Right x, remain1))) $- Parser.try (runStateT st remain0)- force (Cons st) =- Cons $ mapStateT Parser.force st+ else putRemaining (remaining-n) >>+ lift (Parser.skip n)++ warn = Cons . Trans.lift . Parser.warn
− src/Sound/MIDI/Parser/State.hs
@@ -1,56 +0,0 @@-{- |-Very similar to "Sound.MIDI.Parser".--}-module Sound.MIDI.Parser.State- (T, zeroOrMore, StateT(..), ) where--import qualified Sound.MIDI.Parser.Class as Parser--import Control.Monad.State (StateT(..), mapStateT, liftM, liftM2, lift, )---type T st parser = StateT st parser---force :: Parser.C parser =>- T st parser a -> T st parser a-force = mapStateT Parser.force--zeroOrMore :: Parser.C parser =>- T st parser a -> T st parser (Parser.PossiblyIncomplete [a])-zeroOrMore p =- let go =- force $ isEnd >>= \b ->- if b- then return (Nothing, [])- else handleMsg- (\errMsg -> (Just errMsg, []))- (liftM2 (\ x ~(e,xs) -> (e,x:xs)) p go)- in go--{--zeroOrMore :: T st [byte] a -> T st [byte] [a]-zeroOrMore p =- let go =- isEnd >>= \b ->- if b- then return []- else liftM2 (:) p go- in go--}--{- |-In case of an exception, the handler restores the old state.--}-handleMsg :: Parser.C parser =>- (Parser.UserMessage -> a) -> T st parser a -> T st parser a-handleMsg handler action =- StateT $ \s ->- liftM- (either (\e -> (handler e, s)) id)- (Parser.try (runStateT action s))---isEnd :: Parser.C parser =>- T st parser Bool-isEnd = lift $ Parser.isEnd
src/Sound/MIDI/Parser/Status.hs view
@@ -4,12 +4,13 @@ The running state consists of a message code and the message channel. -} module Sound.MIDI.Parser.Status- (T, Status, set, get, run, State.lift,+ (T, Status, set, get, run, lift, Channel, fromChannel, toChannel, ) where -import qualified Sound.MIDI.Parser.State as ParserState+import qualified Sound.MIDI.Parser.Class as Parser -import Control.Monad.State (evalStateT, )+import qualified Control.Monad.Exception.Synchronous as Sync+import Control.Monad.State (StateT, evalStateT, ) import qualified Control.Monad.State as State import Sound.MIDI.Utility (checkRange, )@@ -25,19 +26,23 @@ the parser must keep track of the type and channel of the most recent MIDI Event. This is done by managing a 'Status' in the parser. -}-type T parser a = ParserState.T Status parser a+type T parser = StateT Status parser type Status = Maybe (Int,Channel) -set :: Monad parser => Status -> T parser ()-set = State.put+set :: Monad parser => Status -> Parser.Fallible (T parser) ()+set = State.lift . State.put -get :: Monad parser => T parser Status-get = State.get+get :: Monad parser => Parser.Fallible (T parser) Status+get = State.lift State.get run :: Monad parser => T parser a -> parser a run = flip evalStateT Nothing+++lift :: Monad parser => Parser.Fallible parser a -> Parser.Fallible (T parser) a+lift = Sync.mapExceptionalT State.lift -- * Channel definition
src/Sound/MIDI/Parser/Stream.hs view
@@ -1,17 +1,22 @@ module Sound.MIDI.Parser.Stream (T(..), run, runIncomplete, runPartial, ByteList(..),- PossiblyIncomplete, UserMessage, processReport, ) where+ PossiblyIncomplete, UserMessage, ) where import Control.Monad.State (State(runState), evalState, get, put, liftM, when, )+import Control.Monad.Trans (lift, ) import qualified Sound.MIDI.Parser.Report as Report import qualified Sound.MIDI.Parser.Class as Parser import Sound.MIDI.Parser.Class (UserMessage, PossiblyIncomplete, )+import qualified Sound.MIDI.Parser.Exception as Exception+import qualified Sound.MIDI.Parser.Warning as Warning +-- import qualified Control.Monad.Exception.Synchronous as Sync+ import qualified Sound.MIDI.IO as MIO import Data.Word (Word8)@@ -27,20 +32,22 @@ Instead of using Report and write the monad instance manually, we could also use WriterT monad for warnings and ErrorT monad for failure handling. -}-newtype T str a = Cons {decons :: State str (Report.T a)}+newtype T str a =+ Cons {decons :: Warning.T (State str) a} -runPartial :: T str a -> str -> (Report.T a, str)+runPartial :: Parser.Fallible (T str) a -> str -> (Report.T a, str) runPartial parser input =- flip runState input (decons parser)+ flip runState input $ Warning.run $ decons $ Exception.run parser -run :: ByteStream str => T str a -> str -> Report.T a+run :: ByteStream str => Parser.Fallible (T str) a -> str -> Report.T a run parser input =- flip evalState input $ decons $+ flip evalState input $ Warning.run $ decons $ Exception.run $ (do a <- parser- end <- Parser.isEnd- Parser.force $ when (not end) (warn "unparsed data left over")+ lift $+ Parser.isEnd >>= \end ->+ Parser.warnIf (not end) "unparsed data left over" return a) {- |@@ -49,36 +56,23 @@ and thus further parsing is not possible. -} runIncomplete :: ByteStream str =>- T str (PossiblyIncomplete a) -> str -> Report.T a+ Parser.Partial (Parser.Fallible (T str)) a -> str -> Report.T a runIncomplete parser input = flip run input $- do (me,x) <- parser- Parser.force $ maybe (return ()) warn me- return x+ lift . Parser.warnIncomplete =<< parser fromState :: State str a -> T str a fromState p =- Cons $ liftM (\a -> Report.Cons [] (Right a)) p+ Cons $ lift p instance Monad (T str) where- return x = fromState $ return x- x >>= y = Cons $- decons x >>= \ a ->- case Report.result a of- Left err -> return (Report.Cons (Report.warnings a) (Left err))- Right ar ->- liftM (\b ->--- b{Report.warnings = Report.warnings a ++ Report.warnings b}- -- more lazy- Report.Cons- (Report.warnings a ++ Report.warnings b)- (Report.result b)- ) $- decons (y ar)+ return = Cons . return+ x >>= y = Cons $ decons . y =<< decons x + class ByteStream str where switchL :: a -> (Word8 -> str -> a) -> str -> a drop :: NonNeg.Integer -> str -> str@@ -92,67 +86,27 @@ _ -> n drop n (ByteList xs) = ByteList $ List.genericDrop n xs -instance ByteStream str => Parser.C (T str) where+instance ByteStream str => Parser.EndCheck (T str) where isEnd = fromState $ liftM (switchL True (\ _ _ -> False)) get++instance ByteStream str => Parser.C (T str) where getByte = switchL- (giveUp "unexpected end of data")- (\s ss -> fromState (put ss) >> return s) =<<- fromState get+ (Parser.giveUp "unexpected end of data")+ (\s ss -> lift (fromState (put ss)) >> return s) =<<+ lift (fromState get)+ {- skip n = sequence_ (genericReplicate n Parser.getByte) -} skip n = when (n>0) $- do s <- fromState get+ do s <- lift $ fromState get switchL (Parser.giveUp "skip past end of part")- (\ _ rest -> fromState $ put rest)+ (\ _ rest -> lift $ fromState $ put rest) (drop (n-1) s)- warn = warn- giveUp = giveUp- try = try- force = force --warn :: String -> T str ()-warn text =- Cons $ return $ Report.Cons [text] (Right ())--giveUp :: String -> T str a-giveUp text =- Cons $ return $ Report.Cons [] (Left text)--try :: T str a -> T str (Either UserMessage a)-try =- -- more lazy- Cons .- liftM (\r -> Report.Cons (Report.warnings r) (Right (Report.result r))) .- -- liftM (\r -> r{Report.result = Right (Report.result r)}) .- decons---{- |-Wadler's force function--'force' guarantees that the parser does not fail.-Thus it makes parsing more lazy.-However if the original parser fails though,-then we get an unrecoverable /irrefutable pattern/ error on 'Just'.--}-force :: T str a -> T str a-force p =- Cons $- do ~(Report.Cons w ~(Right x)) <- decons p- return (Report.Cons w (Right x))---{- |-Emit all Report.warnings and throw the error from the report.--}-processReport :: Report.T a -> T str a-processReport report =- mapM_ warn (Report.warnings report) >>- either giveUp return (Report.result report)+ warn = Cons . Warning.warn {-
+ src/Sound/MIDI/Parser/Warning.hs view
@@ -0,0 +1,28 @@+{- |+Handling of warnings.+-}+module Sound.MIDI.Parser.Warning where++import qualified Sound.MIDI.Parser.Report as Report++import qualified Control.Monad.Exception.Synchronous as Sync+import qualified Control.Monad.Writer as Writer+++type T m = Writer.WriterT [Report.UserMessage] m+++run :: Monad m =>+ T m (Sync.Exceptional Report.UserMessage a) -> m (Report.T a)+run act =+ do (exc,warns) <- Writer.runWriterT act+ return $ Report.Cons warns (Sync.toEither exc)++{-+run :: Monad m =>+ T m a -> m (a, [Report.UserMessage])+run = Writer.runWriterT+-}++warn :: Monad m => String -> T m ()+warn text = Writer.tell [text]
test/Main.hs view
@@ -28,6 +28,7 @@ import qualified Data.List as List import Sound.MIDI.Utility (viewR, dropMatch, )+import Control.Monad.Trans (lift, ) import Control.Monad (when, ) import System.Random (mkStdGen, randomR, )@@ -150,7 +151,7 @@ lazinessZeroOrMoreByteList :: NonNeg.Int -> Int -> Bool lazinessZeroOrMoreByteList pos byte = let result =- Report.result $ StreamParser.runIncomplete (Parser.zeroOrMore Parser.getByte) $+ Report.result $ StreamParser.runIncomplete (lift (Parser.zeroOrMore Parser.getByte)) $ StreamParser.ByteList $ repeat $ fromIntegral byte char = show result !! mod (NonNeg.toNumber pos) 1000 in char == char