Skip to content

Commit 7124d62

Browse files
Merge pull request #1 from stephentetley/master
Initial work on a Parser Monad.
2 parents ad6f2c6 + 56a6ecd commit 7124d62

File tree

2 files changed

+195
-0
lines changed

2 files changed

+195
-0
lines changed

src/ZMidi/Internal/ParserMonad.fs

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
2+
namespace ZMidi.Internal
3+
4+
module ParserMonad =
5+
6+
open System.IO
7+
8+
open ZMidi.Internal.Utils
9+
10+
/// Status is either OFF of the previous VoiceEvent * Channel.
11+
type VoiceEvent =
12+
| StatusOff
13+
| NoteOn of byte
14+
| NoteOff of byte
15+
| NoteAftertoucuh of byte
16+
| Control of byte
17+
| Program of byte
18+
| ChannelAftertouch of byte
19+
| PitchBend of byte
20+
21+
22+
type Pos = int
23+
24+
type ErrMsg = string
25+
26+
type State =
27+
{ Position: Pos
28+
RunningStatus: VoiceEvent
29+
}
30+
31+
32+
type ParserMonad<'a> =
33+
private ParserMonad of (byte [] -> State -> Result<'a * State, ErrMsg> )
34+
35+
let inline private apply1 (parser : ParserMonad<'a>)
36+
(midiData : byte[])
37+
(state : State) : Result<'a * State, ErrMsg> =
38+
let (ParserMonad fn) = parser in fn midiData state
39+
40+
let inline mreturn (x:'a) : ParserMonad<'a> =
41+
ParserMonad <| fun _ st -> Ok (x, st)
42+
43+
let inline private bindM (parser : ParserMonad<'a>)
44+
(next : 'a -> ParserMonad<'b>) : ParserMonad<'b> =
45+
ParserMonad <| fun input state ->
46+
match apply1 parser input state with
47+
| Error msg -> Error msg
48+
| Ok (ans, st1) -> apply1 (next ans) input st1
49+
50+
let mzero () : ParserMonad<'a> =
51+
ParserMonad <| fun _ _ -> Error "mzero"
52+
53+
let inline mplus (parser1 : ParserMonad<'a>) (parser2 : ParserMonad<'a>) : ParserMonad<'a> =
54+
ParserMonad <| fun input state ->
55+
match apply1 parser1 input state with
56+
| Error _ -> apply1 parser2 input state
57+
| Ok res -> Ok res
58+
59+
let inline private delayM (fn:unit -> ParserMonad<'a>) : ParserMonad<'a> =
60+
bindM (mreturn ()) fn
61+
62+
63+
type ParserBuilder() =
64+
member self.Return x = mreturn x
65+
member self.Bind (p,f) = bindM p f
66+
member self.Zero () = mzero ()
67+
member self.Combine (ma, mb) = mplus ma mb
68+
member self.ReturnFrom (ma:ParserMonad<'a>) : ParserMonad<'a> = ma
69+
70+
let (parseMidi:ParserBuilder) = new ParserBuilder()
71+
72+
/// Run the parser on a file.
73+
let runParseMidi (ma : ParserMonad<'a>) (inputPath : string) : Result<'a, ErrMsg> =
74+
use stream = File.Open(path = inputPath, mode = FileMode.Open, access = FileAccess.Read)
75+
use memory = new MemoryStream()
76+
stream.CopyTo(memory)
77+
let input : byte [] = memory.ToArray()
78+
match apply1 ma input { Position = 0; RunningStatus = StatusOff} with
79+
| Ok (ans, _) -> Ok ans
80+
| Error msg -> Error msg
81+
82+
/// Throw a parse error
83+
let parseError (genMessage : Pos -> string) : ParserMonad<'a> =
84+
ParserMonad <| fun _ st -> Error (genMessage st.Position)
85+
86+
/// Run the parser, if it fails swap the error message.
87+
let ( <??> ) (parser : ParserMonad<'a>) (genMessage : Pos -> string) : ParserMonad<'a> =
88+
ParserMonad <| fun input st ->
89+
match apply1 parser input st with
90+
| Ok result -> Ok result
91+
| Error _ -> Error (genMessage st.Position)
92+
93+
94+
let getRunningEvent : ParserMonad<VoiceEvent> =
95+
ParserMonad <| fun _ st -> Ok (st.RunningStatus , st)
96+
97+
let setRunningEvent (runningStatus : VoiceEvent) : ParserMonad<unit> =
98+
ParserMonad <| fun _ st -> Ok ((), { st with RunningStatus = runningStatus })
99+
100+
let getPos : ParserMonad<int> =
101+
ParserMonad <| fun _ st -> Ok (st.Position, st)
102+
103+
let peek : ParserMonad<byte> =
104+
ParserMonad <| fun input st ->
105+
try
106+
let a1 = input.[st.Position]
107+
Ok (a1, st)
108+
with
109+
| _ -> Error "peek - position error"
110+
111+
/// Conditionally get a byte (word8) . Fails if input is finished.
112+
/// Consumes data on if predicate succeeds, does not consume if
113+
/// predicate fails.
114+
let cond (test : byte -> bool) : ParserMonad<byte option> =
115+
ParserMonad <| fun input st ->
116+
try
117+
let a1 = input.[st.Position]
118+
if test a1 then
119+
Ok (Some a1, st)
120+
else Ok (None, st)
121+
with
122+
| _ -> Error "cond - position error"
123+
124+
let count (length : int) (parser : ParserMonad<'a>) : ParserMonad<'a []> =
125+
ParserMonad <| fun input state ->
126+
let rec work (i : int)
127+
(st : State)
128+
(fk : ErrMsg -> Result<'a list * State, ErrMsg>)
129+
(sk : State -> 'a list -> Result<'a list * State, ErrMsg>) =
130+
if i <= 0 then
131+
sk st []
132+
else
133+
match apply1 parser input st with
134+
| Error msg -> fk msg
135+
| Ok (a1, st1) ->
136+
work (i-1) st1 fk (fun st2 ac ->
137+
sk st2 (a1 :: ac))
138+
work length state (fun msg -> Error msg) (fun st ac -> Ok (ac, st))
139+
|> Result.map (fun (ans, st) -> (List.toArray ans, st))
140+
141+
142+
/// Drop a byte (word8)
143+
let dropByte : ParserMonad<unit> =
144+
ParserMonad <| fun input st ->
145+
if st.Position < input.Length then
146+
Ok ((), { st with Position = st.Position + 1 })
147+
else
148+
Error "dropByte - no more data"
149+
150+
/// Parse a byte (Word8).
151+
let readByte : ParserMonad<byte>=
152+
ParserMonad <| fun input st ->
153+
try
154+
let a1 = input.[st.Position]
155+
Ok (a1, { st with Position = st.Position + 1 })
156+
157+
with
158+
| _ -> Error (sprintf "readByte - no more data at %i" st.Position)
159+
160+
/// Parse a single byte char.
161+
let readChar : ParserMonad<char> =
162+
parseMidi {
163+
let! a = readByte
164+
return (char a)
165+
}
166+
167+
/// Parse a string of the given length.
168+
let readString (length : int) : ParserMonad<string> =
169+
parseMidi {
170+
let! arr = count length readChar
171+
return (System.String arr)
172+
}
173+
<??> sprintf "readString failed at %i"
174+
175+
176+
// Parse a uint16 (big endian).
177+
let readUint16be : ParserMonad<uint16>=
178+
parseMidi {
179+
let! a = readByte
180+
let! b = readByte
181+
return uint16be a b
182+
}
183+
<??> sprintf "uint16be: failed at %i"

src/ZMidi/Internal/Utils.fs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace ZMidi.Internal
2+
3+
module Utils =
4+
5+
open System.IO
6+
7+
8+
/// Build a Word16 (big endian).
9+
let uint16be (a : byte) (b : byte) : uint16 =
10+
let a1 = uint16 a
11+
let b1 = uint16 b
12+
(a1 <<< 8) + b1

0 commit comments

Comments
 (0)