SharpBoomerang


Parsing JSON

FParsec has JSON parsing as one of its tutorials. I adapted the code from the tutorial and ported it to SharpBoomerang. This document will be fleshed out with more discussion in the future.

 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: 
type Json =
    | JNull
    | JBool   of bool
    | JString of string
    | JNumber of float

    | JList   of Json seq
    | JObject of Map<string, Json>

/// Boomerangs a JNull
let bjnull = blit %"null" >>% JNull

/// Boomerangs a JBool
let bjbool = (blit %"true" >>% JBool true) <.>
             (blit %"false" >>% JBool false)

/// Boomerangs a (possibly escaped) character. Doesn't handle Unicode escapes.
let bjchr =
    // Define a boomerang to parse any character that isn't a backslash
    let bchrnoslash = Channel.expectFn "no slash" (fun c -> c <> '\\') >> bchr

    // Boomerang a possibly-escaped character with the first-successful parse operator, <.>
    (blit %"\\b" >>% '\b') <.>
    (blit %"\\f" >>% '\f') <.>
    (blit %"\\n" >>% '\n') <.>
    (blit %"\\r" >>% '\r') <.>
    (blit %"\\t" >>% '\t') <.>
    (blit %"\\\"" >>% '"') <.>
    // Any character that isn't a slash simply yields itself
    bchrnoslash            <.>
    // Any other character prefixed by a slash simply yields that character
    //  This rule must come after the previous one to prevent a slash from always being printed.
    (blit %"\\" >>. bchr)

/// Boomerangs a JString
let bjstr =
    // Define an Iso for converting a character sequence to a string
    let charsToStr : Iso<_,_> = ((fun chrs -> String(Seq.toArray chrs)),
                                 (fun str  -> str.ToCharArray() :> char seq))
    // Boomerang a quoted JSON string
    blit %"\"" >>. (+.bjchr .>>% charsToStr) .>> blit %"\"" .>>% Iso.ofFn(fun str -> JString str)

/// Boomerangs a JNumber. Doesn't handle exponent notation
let bjnum = bfloat .>>% Iso.ofFn(fun num -> JNumber num)

/// Boomerangs a JList
// Note that we need to make the arg explicit here to make f# happy :/
let rec bjlist jlist =
    blit %"[" >>. bslist bjson (blit %",") .>> blit %"]" .>>% Iso.ofFn(fun seq -> JList seq) <| jlist

/// Boomerangs a JObject
// Again note that we need to make the arg explicit here to make f# happy :/
and bjobj jobj =
    // Boomerang a key-value pair
    let bkv = bjstr .>> bws0 .>> blit %":" .>>. bjson .>>% ((fun (JString k, v) -> (k, v)), (fun (k, v) -> (JString k, v)))

    // Boomerang the object
    blit %"{" >> bws0 >>. bslist bkv (bws0 >> blit %"," >> bws0) .>> bws0 .>> blit %"}" .>>% Iso.ofFn(fun seq -> seq |> Map.ofSeq |> JObject) <| jobj

/// Boomerangs a Json
and bjson =
    bws0    >>. // optional whitespace
    (bjnull <.>
     bjbool <.>
     bjstr  <.>
     bjnum  <.>
     bjlist <.>
     bjobj) .>>
    bws0 // optionally ending in whitespace


let parseJson = StringInputChannel >> parser bjson
let printJson = stringPrinter bjson

Now we can parse some JSON like so:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
let json = parseJson """
{
    "str": "value",
    "num": 10.5,
    "bln": true,
    "nul": null,
    "lst": [4, 5, "a", "b"]
}
"""

This yields an AST:

JObject
  (map
     [("bln", JBool true);
      ("lst", JList [|JNumber 4.0; JNumber 5.0; JString "a"; JString "b"|]);
      ("nul", JNull); ("num", JNumber 10.5); ("str", JString "value")])

Then we can pass the same value back to printJson to print it back out again:

1: 
printJson json
"{"bln":true,"lst":[4,5,"a","b"],"nul":null,"num":10.5,"str":"value"}"
namespace System
namespace SharpBoomerang
module Combinators

from SharpBoomerang
type Json =
  | JNull
  | JBool of bool
  | JString of string
  | JNumber of float
  | JList of seq<Json>
  | JObject of Map<string,Json>

Full name: JSON.Json
union case Json.JNull: Json
union case Json.JBool: bool -> Json
type bool = Boolean

Full name: Microsoft.FSharp.Core.bool
union case Json.JString: string -> Json
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = String

Full name: Microsoft.FSharp.Core.string
union case Json.JNumber: float -> Json
Multiple items
val float : value:'T -> float (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.float

--------------------
type float = Double

Full name: Microsoft.FSharp.Core.float

--------------------
type float<'Measure> = float

Full name: Microsoft.FSharp.Core.float<_>
union case Json.JList: seq<Json> -> Json
Multiple items
val seq : sequence:seq<'T> -> seq<'T>

Full name: Microsoft.FSharp.Core.Operators.seq

--------------------
type seq<'T> = Collections.Generic.IEnumerable<'T>

Full name: Microsoft.FSharp.Collections.seq<_>
union case Json.JObject: Map<string,Json> -> Json
Multiple items
module Map

from Microsoft.FSharp.Collections

--------------------
type Map<'Key,'Value (requires comparison)> =
  interface IEnumerable
  interface IComparable
  interface IEnumerable<KeyValuePair<'Key,'Value>>
  interface ICollection<KeyValuePair<'Key,'Value>>
  interface IDictionary<'Key,'Value>
  new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
  member Add : key:'Key * value:'Value -> Map<'Key,'Value>
  member ContainsKey : key:'Key -> bool
  override Equals : obj -> bool
  member Remove : key:'Key -> Map<'Key,'Value>
  ...

Full name: Microsoft.FSharp.Collections.Map<_,_>

--------------------
new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
val bjnull : (channel<Json> -> Context -> Context)

Full name: JSON.bjnull


 Boomerangs a JNull
val blit : str:channel<string> -> (Context -> Context)

Full name: SharpBoomerang.Combinators.blit


 Boomerangs a literal string
val bjbool : (channel<Json> -> Context -> Context)

Full name: JSON.bjbool


 Boomerangs a JBool
val bjchr : (channel<char> -> Context -> Context)

Full name: JSON.bjchr


 Boomerangs a (possibly escaped) character. Doesn't handle Unicode escapes.
val bchrnoslash : (channel<char> -> Context -> Context)
module Channel

from SharpBoomerang
val expectFn : desc:string -> check:('t -> bool) -> ch:IChannel<'t> -> IChannel<'t>

Full name: SharpBoomerang.Channel.expectFn


 Returns a channel that expects a certain value to be read or written
  to/from the given channel. If a different value is read or written,
  throws an exception with the given description. The expected value is determined by a function.
val c : char
val bchr : chr:channel<char> -> (Context -> Context)

Full name: SharpBoomerang.Combinators.bchr


 Boomerangs a single character
val bjstr : (channel<Json> -> Context -> Context)

Full name: JSON.bjstr


 Boomerangs a JString
val charsToStr : Iso<seq<char>,String>
Multiple items
type Iso =
  private new : unit -> Iso
  static member ofFn : expr:Expr<('a -> 'b)> -> Iso<'a,'b>
  static member oneWay : fn:('a -> 'b) -> Iso<'a,'b>

Full name: SharpBoomerang.Iso

--------------------
type Iso<'a,'b> = ('a -> 'b) * ('b -> 'a)

Full name: SharpBoomerang.Iso<_,_>


 Total, one-to-one isomorphism of a <> b
val chrs : seq<char>
Multiple items
type String =
  new : value:char -> string + 7 overloads
  member Chars : int -> char
  member Clone : unit -> obj
  member CompareTo : value:obj -> int + 1 overload
  member Contains : value:string -> bool
  member CopyTo : sourceIndex:int * destination:char[] * destinationIndex:int * count:int -> unit
  member EndsWith : value:string -> bool + 2 overloads
  member Equals : obj:obj -> bool + 2 overloads
  member GetEnumerator : unit -> CharEnumerator
  member GetHashCode : unit -> int
  ...

Full name: System.String

--------------------
String(value: nativeptr<char>) : unit
String(value: nativeptr<sbyte>) : unit
String(value: char []) : unit
String(c: char, count: int) : unit
String(value: nativeptr<char>, startIndex: int, length: int) : unit
String(value: nativeptr<sbyte>, startIndex: int, length: int) : unit
String(value: char [], startIndex: int, length: int) : unit
String(value: nativeptr<sbyte>, startIndex: int, length: int, enc: Text.Encoding) : unit
module Seq

from Microsoft.FSharp.Collections
val toArray : source:seq<'T> -> 'T []

Full name: Microsoft.FSharp.Collections.Seq.toArray
val str : String
String.ToCharArray() : char []
String.ToCharArray(startIndex: int, length: int) : char []
Multiple items
val char : value:'T -> char (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.char

--------------------
type char = Char

Full name: Microsoft.FSharp.Core.char
static member Iso.ofFn : expr:Quotations.Expr<('a -> 'b)> -> Iso<'a,'b>


 Given a simple function, `'a -> 'b`, attempts to derive the inverse implicitly
val bjnum : (channel<Json> -> Context -> Context)

Full name: JSON.bjnum


 Boomerangs a JNumber. Doesn't handle exponent notation
val bfloat : Boomerang<float>

Full name: SharpBoomerang.Combinators.bfloat


 Boomerangs a possibly-negative floating point number
val num : float
val bjlist : jlist:IChannel<Json> -> (Context -> Context)

Full name: JSON.bjlist


 Boomerangs a JList
val jlist : IChannel<Json>
val bslist : l:Boomerang<'t> -> s:Boomerang -> list:IChannel<seq<'t>> -> ctx:Context -> Context

Full name: SharpBoomerang.Combinators.bslist


 Boomerangs the given boomerang repeatedly, separated by the given separator
  Similar to the channel-propagating greedy one or more operator (!+.), but with the given separator
val bjson : Boomerang<Json>

Full name: JSON.bjson


 Boomerangs a Json
Multiple items
val seq : seq<Json>

--------------------
type seq<'T> = Collections.Generic.IEnumerable<'T>

Full name: Microsoft.FSharp.Collections.seq<_>
val bjobj : jobj:IChannel<Json> -> (Context -> Context)

Full name: JSON.bjobj


 Boomerangs a JObject
val jobj : IChannel<Json>
val bkv : (channel<string * Json> -> Context -> Context)
val bws0 : Boomerang

Full name: SharpBoomerang.Combinators.bws0


 Boomerangs a greedy sequence of zero or more whitespace characters.
  When printing, no whitespace is printed.
val k : string
val v : Json
Multiple items
val seq : seq<string * Json>

--------------------
type seq<'T> = Collections.Generic.IEnumerable<'T>

Full name: Microsoft.FSharp.Collections.seq<_>
val ofSeq : elements:seq<'Key * 'T> -> Map<'Key,'T> (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.ofSeq
val parseJson : (string -> Json)

Full name: JSON.parseJson
Multiple items
type StringInputChannel =
  interface ICharChannel
  new : str:string -> StringInputChannel
  member Mark : unit -> IMark
  member Read : ret:(char -> 'a0) -> 'a0
  member EndOfStream : bool
  member Position : Position

Full name: SharpBoomerang.StringInputChannel

--------------------
new : str:string -> StringInputChannel
val parser : b:Boomerang<'a> -> inputCh:ICharChannel -> 'a

Full name: SharpBoomerang.Combinators.parser
val printJson : (Json -> string)

Full name: JSON.printJson
val stringPrinter : b:Boomerang<'a> -> value:'a -> string

Full name: SharpBoomerang.Combinators.stringPrinter
val json : Json

Full name: JSON.json
Fork me on GitHub