- Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathProgram.fs
More file actions
Latest commit
356 lines (300 loc) · 12.7 KB
/
Copy pathProgram.fs
File metadata and controls
356 lines (300 loc) · 12.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
open System
// Here we define a Parser of *T* as a **Type** that takes a *string* and an *int* (a position
// within that string), and returns an **Option** which is a tuple of (*T* and *int*).
// See also: https://fsharpforfunandprofit.com/posts/designing-with-types-single-case-dus/
// 1+2
typeParser<'T>= P of(string->int->Option<'T*int>)
// The basic building block, *pchar*. This fundamental function is of type **P**.
// It takes a string *str* and a position in the string *pos*, and returns
// either Some tuple of the character at *pos* and the incremented *pos*, or None.
// Note that is the _only_ place in the code where we compare *pos* with the
// length of *str*. This test is very neatly encapsulated within this one function.
//
letpchar:Parser<char>=
P <|fun str pos ->
if pos < str.Length then
Some (str.[pos], pos+1)
else
None
//This is a helper function that allows us to run *aParser* simply.
//
letprun(P aParser)str = aParser str 0
// The functions *preturn*, and *pfail* may seem pointless, but are in fact very helpful.
// *preturn* takes a *value* and always returns Some tuple of *value* and an unaltered *pos*.
//
letpreturn value :Parser<'T>=
P <|fun str pos -> Some (value, pos)
// The function *pfail* takes unit and always returns None. See *psatisfy* later.
//
letpfail():Parser<'T>=
P <|fun str pos -> None
// *pbind* follows a standard functional pattern. Here we want to take the output of
// one parser and feed that as the input into another parser. The problem is the inputs
// and outputs are incompatible! We defined a parser as producing an **Option<'T * int>**
// and a parser, of course, takes a string and an int as its inputs, not an Option of
// Some or None (or simply put, success or failure)!
//`
//` 
// We need to somehow provide a "combiner" that allows us to "chain" parsers
// together. The function *pbind* provides that combiner:
//
// * *ufunc* is a function that takes a **type** T and returns a parser of some type U
// * *tparser* is a parser of the _same_ type U
//
// We run *str* and *pos* through the parser *tparser*. If that parser succeeds, it
// returns Some *tvalue* and *tpos*, we pass *tvalue* as an argument to *ufunc*.
// This function gives us back a new parser determined by the **type** of *tvalue*,
// not its value. It is to this new parser (*uparser*) that we subsequently pass *str*
// and the new *tpos*. of course, if *tparser* fails, then we just return None.
//
letpbind(ufunc :'T ->Parser<'U>)(P tparser):Parser<'U>=
P <|fun str pos ->
match tparser str pos with
| None -> None
| Some (tvalue, newPos)->
let(P uparser)= ufunc tvalue
uparser str newPos
// Try it out:-
// let p = pchar |> pbind (fun first -> pchar |> pbind (fun second -> preturn (first, second)))
// prun p "Hello Marten from F#!" |> printfn "%A"
//
// This should produce **Some(('H','e'), 2)**, as it "chained" two *pchar* parsers together,
// and so gave us the first and second characters of the string, and moved the position
// in the string to 2.
// Given two parsers *uparser* and *tparser*, combine them using *pbind* as follows.
// Studying the definition of *pbind*, its clear that the lambda **fun _ -> uparser**,
// which is our *ufunc* in the *pbind* function, always gives back a *uparser*,
// no matter the type of the value returned from *tparser* (_ is the discard in F#).
// This means that **tparser |> pcombine uparser** runs *tparser*, discards the result,
// and returns the result of *uparser*.
//
letpcombine(uparser :Parser<'U>)(tparser :Parser<'T>):Parser<'U>= tparser |> pbind (fun _ -> uparser)
letpkeepRight uparser tparser = pcombine uparser tparser
letpkeepLeft(uparser :Parser<'U>)(tparser :Parser<'T>):Parser<'T>=
tparser |> pbind (fun tv -> uparser |> pbind (fun _ -> preturn tv))
// *pmany* takes a parser of a list of (*tparser*), and returns Some tuple of values and
// the position *pos* at which the parser failed. We can use this to parser 0 or more
// int in a string, for example: *prun pint "42Hello Marten from F#!" |> printfn "%A"*
//
letpmany(P tparser):Parser<'Tlist>=
P <|fun str pos ->
let recloop values currentPos =
match tparser str currentPos with
| None -> Some (List.rev values, currentPos)// reverse the list to regain proper ordering
| Some (tvalue, tpos)-> loop (tvalue::values) tpos
loop [] pos
// ((1 + 2) + 3) + 4
// int -> (string -> float)
letpchainl1(term :Parser<'T>)(sep :Parser<'T->'T->'T>):Parser<'T>=
let(P termfun)= term
let(P sepfun)= sep
P <|fun str pos ->
let recloop aggr currentPos =
match sepfun str currentPos with
| None -> Some (aggr, currentPos)
| Some (sepCombiner, sepPos)->
match termfun str sepPos with
| None -> None
| Some (termValue, termPos)-> loop (sepCombiner aggr termValue) termPos
match termfun str pos with
| None -> None
| Some (termValue, termPos)-> loop termValue termPos
// A computation expression; this will make it simpler for us to build up more
// complex parsers. This is a standard pattern in functional programming. Notice
// how it uses *pbind*, *pcombine* and *preturn* that we defined earlier.
//
typeParserBuilder()=
class
// Enables let! - binds the **result** of a call to another computation expression to a name
// (compare with *await* in C#, that unwraps a Task<T> to give a result of T)
memberx.Bind(tparser,ufunc)= pbind ufunc tparser
// Enables do! - used for calling another computation expression that returns a unit-like
// type (as defined by the x.Zero member below)
memberx.Combine(tparser,uparser)= pcombine uparser tparser
// Enables return - returns *value*
memberx.Return value = preturn value
// Enables return! - realizes the value of a computation expression and wraps that result
// as a Parser<'T>
memberx.ReturnFrom p = p : Parser<'T>
// Allows if...then expression with no else - returns unit
memberx.Zero()= preturn ()
end
letparser= ParserBuilder ()
// *pmap* takes a mapping function that maps a type T to a type U, and a parser of T.
// The mapping function is applied to the value parsed and is then returned. An F#
// *map* is functionally equivalent to Select in LINQ.
//
// For an example of using *pmap*, see *pint* below.
//
letpmap mappingFunc tparser =
parser {
let!value= tparser
return mappingFunc value
}
letppair uparser tparser =
parser {
let!first= tparser
let!second= uparser
return first, second
}
// (\d|\w)
letporElse(P uparser)(P tparser)=
P <|fun str pos ->
match tparser str pos with
| None -> uparser str pos
| Some (tvalue, tpos)-> Some (tvalue, tpos)
typeParser<'T>with
static member(>>=)(t, uf)= pbind uf t
static member(>>.)(t,u)= pkeepRight u t
static member(.>>)(t,u)= pkeepLeft u t
static member(.>>.)(t,u)= ppair u t
static member(|>>)(t,m)= pmap m t
static member(<|>)(t,u)= porElse u t
// From this point on, we express parsers using the computation expression above, as it
// simplifies combining parsers for many people.
// Similar to *pmany*, this parser works against 1 or more 'T, rather than 0 or more.
// For example, *pmany1 pletter* is a parse of 1 or more consecutive letters in a string.
//
letpmany1 tparser =
parser {
let!head= tparser
let!tail= pmany tparser
return head::tail
}
letpcreateParserForwardedToRef<'T>():Parser<'T>*Parser<'T> ref =
letdummyParser=
P <|fun str pos ->
failwith "Forwarded parser not initialized"
letrefToParser= ref dummyParser
letforwardingParser=
P <|fun str pos ->
let(P p)=!refToParser
p str pos
forwardingParser, refToParser
letpsatisfy(satisfy :char ->bool):Parser<char>=
parser {
let!char= pchar // parse a char
if satisfy char then// if the satisfy function evaluates to true
return char // return the char
else
return! pfail ()// else fail
}
// A parser that parses a digit or fails.
//
letpdigit= psatisfy Char.IsDigit
// A parser that parses a letter or fails.
//
letpletter= psatisfy Char.IsLetter
// A parser that parses whitespace or fails.
//
letpwhitespace= psatisfy Char.IsWhiteSpace
// A useful helper function that, given a list of char digits, returns an single
// integer value. For example: // ['1'; '2'; '0'] becomes 120.
// Think LINQs Aggregate method in C#.
//
letmakeint listOfChars =
letfoldingFunc aggr ch =
aggr *10+(int ch - int '0')
listOfChars |> List.fold foldingFunc 0
// Pass into *pmap* a mapping function that turns a list of digits into an integer,
// and a parser that can parse one or more digits. The result is a strongly-typed
// Parser<int> that can parse out a set of consecutive digits in a string, and produce
// an integer; how cool is that?
//
letpint= pmany1 pdigit |> pmap makeint
// A parser of strings, using *pmany1* to get a list of characters, and then *pmap* to
// map that list to a single string. *tparser* could be *tdigit* or *tletter*, for
// example. Notice how the type of *pstring* is Parser<string>!
//
letpstring(tparser :Parser<char>):Parser<string>=
pmany1 tparser |> pmap (fun charList -> charList |> List.toArray |> String)
// A parser that skips over a given character.
letpskipchar ch =
parser {
let!char= pchar
if ch = char then
return()
else
return! pfail ()
}
//let padd =
// parser {
// let! first = pint // first integer
// do! pskipchar '+' // using do! because skipchar returns unit-like value
// let! second = pint // second integer
// return first + second // return the sum
// }
// Parser for a key-value pair of the form "<string>=<int>"
letpkeyvalue=
parser {
let!first= pstring pletter
do! pskipchar '='
let!second= pint
return(first, second)
}
// Parser for many key-value pairs e.g. "<string>=<int> <string>=<int> <string>=<int>"
letpkeyvalues=
lethelper=
parser {
let!whitespace= pmany1 pwhitespace
return! pkeyvalue
}
parser{
let!first= pkeyvalue
let!tail= pmany helper
return first::tail
}
typeBinaryOp= Add|Subtract|Multiply|Divide
// AST = Abstract Syntax Tree
typeAST=
| Const ofint
| Variable ofstring
| BinaryOperation ofAST*BinaryOp*AST
// 1+x*(3 + 1)
letx=
BinaryOperation (Const 1, Add, BinaryOperation (Variable "x", Multiply, Const 3))
// 1*(1+3)
letpForwardedAST,pRefToForwardedAST = pcreateParserForwardedToRef<AST>()
letpsubExpr= pskipchar '('>>. pForwardedAST .>> pskipchar ')'
letpconst= pint |>> Const
//let pvariable = pchar |>> (fun c -> Variable (string c))
letpvariable= pstring pletter |>> Variable
letpop opChar operator =
pskipchar opChar
|>>fun c ->fun leftTree rightTree -> BinaryOperation (leftTree, operator, rightTree)
letpadd= pop '+' Add
letpsubtract= pop '-' Subtract
letpmultiply= pop '*' Multiply
letpdivide= pop '/' Divide
letpAllOp= padd <|> psubtract <|> pmultiply <|> pdivide
letpmultiOrDivide= pmultiply <|> pdivide
letpaddOrSubtract= padd <|> psubtract
letpterm= psubExpr <|> pconst <|> pvariable
letpchainMultiDivide= pchainl1 pterm pmultiOrDivide
letpchainAddSubtract= pchainl1 pchainMultiDivide paddOrSubtract
do
pRefToForwardedAST := pchainAddSubtract
letpAST= pchainAddSubtract
// https://github.com/stephan-tolksdorf/fparsec
// https://github.com/stephan-tolksdorf/fparsec/blob/master/Samples/JSON/parser.fs
// https://www.quanttec.com/fparsec/tutorial.html#
// https://github.com/mrange/fable-formlets/blob/master/WHY.md
[<EntryPoint>]
letmain argv =
//let p = pchar |> pbind (fun f -> pchar |> pbind (fun s -> preturn (f, s)))
//let p = pchar |> pcombine pchar
//let p =
// parser {
// let! first = pchar
// let! second = pchar
// let! third = pchar
// return first, second, third
// }
letp= pAST
prun p "1"|> printfn "%A"
prun p "1+2"|> printfn "%A"
prun p "1+2-3*4"|> printfn "%A"
prun p "(x+(2-3))*y"|> printfn "%A"
//prun pint "42Hello Marten from F#!" |> printfn "%A"
//prun pkeyvalues "answer=42 question=22 test=1" |> printfn "%A"
0// return an integer exit code