JsonParser (Polyglot)¶

In [1]:
#!import ../../lib/fsharp/Notebooks.livemd
In [2]:
#!import ../../lib/fsharp/Testing.livemd
In [3]:
#!import ../../lib/fsharp/Common.fs
In [4]:
#!import Parser.fs
In [5]:
open Common
open Parser

JsonParser¶

In [6]:
(*
// --------------------------------
JSON spec from http://www.json.org/
// --------------------------------

The JSON spec is available at [json.org](http://www.json.org/). I'll paraphase it here:

* A `value` can be a `string` or a `number` or a `bool` or `null` or an `object` or an `array`.
  * These structures can be nested.
* A `string` is a sequence of zero or more Unicode characters, wrapped in double quotes, using backslash escapes.
* A `number` is very much like a C or Java number, except that the octal and hexadecimal formats are not used.
* A `boolean` is the literal `true` or `false`
* A `null` is the literal `null`
* An `object` is an unordered set of name/value pairs.
  * An object begins with { (left brace) and ends with } (right brace).
  * Each name is followed by : (colon) and the name/value pairs are separated by , (comma).
* An `array` is an ordered collection of values.
  * An array begins with [ (left bracket) and ends with ] (right bracket).
  * Values are separated by , (comma).
* Whitespace can be inserted between any pair of tokens.
*)
In [7]:
///- --test

let inline parserEqual (expected : ParseResult<'a>) (actual : ParseResult<'a * Input>) =
    match actual, expected with
    | Success (_actual, _), Success _expected ->
        printResult actual
        _actual |> _assertEqual _expected
    | Failure (l1, e1, p1), Failure (l2, e2, p2) when l1 = l2 && e1 = e2 && p1 = p2 ->
        printResult actual
    | _ ->
        printfn $"Actual: {actual}"
        printfn $"Expected: {expected}"
        failwith "Parse failed"
    actual

JValue¶

In [8]:
type JValue =
    | JString of string
    | JNumber of float
    | JBool   of bool
    | JNull
    | JObject of Map<string, JValue>
    | JArray  of JValue list
In [9]:
let jValue, jValueRef = createParserForwardedToRef<JValue> ()

jNull¶

In [10]:
let jNull =
    pstring "null"
    >>% JNull
    <?> "null"
In [11]:
///- --test

jValueRef <|
    choice
        [
            jNull
        ]
In [12]:
///- --test

run jValue "null"
|> parserEqual (Success JNull)
JNull
JNull
Out[12]:
Success (JNull, { lines = [|"null"|]
                  position = { line = 0
                               column = 4 } })
In [13]:
///- --test

run jNull "nulp"
|> parserEqual (
    Failure (
        "null",
        "Unexpected 'p'",
        { currentLine = "nulp"; line = 0; column = 3 }
    )
)
Line:0 Col:3 Error parsing null
nulp
   ^Unexpected 'p'
Out[13]:
Failure ("null", "Unexpected 'p'", { currentLine = "nulp"
                                     line = 0
                                     column = 3 })

jBool¶

In [14]:
let jBool =
    let jtrue =
        pstring "true"
        >>% JBool true
    let jfalse =
        pstring "false"
        >>% JBool false

    jtrue <|> jfalse
    <?> "bool"
In [15]:
///- --test

jValueRef <|
    choice
        [
            jNull
            jBool
        ]
In [16]:
///- --test

run jBool "true"
|> parserEqual (Success (JBool true))
JBool true
JBool true
Out[16]:
Success (JBool true, { lines = [|"true"|]
                       position = { line = 0
                                    column = 4 } })
In [17]:
///- --test

run jBool "false"
|> parserEqual (Success (JBool false))
JBool false
JBool false
Out[17]:
Success (JBool false, { lines = [|"false"|]
                        position = { line = 0
                                     column = 5 } })
In [18]:
///- --test

run jBool "truX"
|> parserEqual (
    Failure (
        "bool",
        "Unexpected 't'",
        { currentLine = "truX"; line = 0; column = 0 }
    )
)
Line:0 Col:0 Error parsing bool
truX
^Unexpected 't'
Out[18]:
Failure ("bool", "Unexpected 't'", { currentLine = "truX"
                                     line = 0
                                     column = 0 })

jUnescapedChar¶

In [19]:
let jUnescapedChar =
    satisfy (fun ch -> ch <> '\\' && ch <> '\"') "char"
In [20]:
///- --test

run jUnescapedChar "a"
|> parserEqual (Success 'a')
'a'
'a'
Out[20]:
Success ('a', { lines = [|"a"|]
                position = { line = 0
                             column = 1 } })
In [21]:
///- --test

run jUnescapedChar "\\"
|> parserEqual (
    Failure (
        "char",
        "Unexpected '\\'",
        { currentLine = "\\"; line = 0; column = 0 }
    )
)
Line:0 Col:0 Error parsing char
\
^Unexpected '\'
Out[21]:
Failure ("char", "Unexpected '\'", { currentLine = "\"
                                     line = 0
                                     column = 0 })

jEscapedChar¶

In [22]:
let jEscapedChar =
    [
        ("\\\"",'\"')
        ("\\\\",'\\')
        ("\\/",'/')
        ("\\b",'\b')
        ("\\f",'\f')
        ("\\n",'\n')
        ("\\r",'\r')
        ("\\t",'\t')
    ]
    |> List.map (fun (toMatch, result) ->
        pstring toMatch >>% result
    )
    |> choice
    <?> "escaped char"
In [23]:
///- --test

run jEscapedChar "\\\\"
|> parserEqual (Success '\\')
'\\'
'\\'
Out[23]:
Success ('\\', { lines = [|"\\"|]
                 position = { line = 0
                              column = 2 } })
In [24]:
///- --test

run jEscapedChar "\\t"
|> parserEqual (Success '\t')
'\009'
'\009'
Out[24]:
Success ('\009', { lines = [|"\t"|]
                   position = { line = 0
                                column = 2 } })
In [25]:
///- --test

run jEscapedChar @"\\"
|> parserEqual (Success '\\')
'\\'
'\\'
Out[25]:
Success ('\\', { lines = [|"\\"|]
                 position = { line = 0
                              column = 2 } })
In [26]:
///- --test

run jEscapedChar @"\n"
|> parserEqual (Success '\n')
'\010'
'\010'
Out[26]:
Success ('\010', { lines = [|"\n"|]
                   position = { line = 0
                                column = 2 } })
In [27]:
///- --test

run jEscapedChar "a"
|> parserEqual (
    Failure (
        "escaped char",
        "Unexpected 'a'",
        { currentLine = "a"; line = 0; column = 0 }
    )
)
Line:0 Col:0 Error parsing escaped char
a
^Unexpected 'a'
Out[27]:
Failure ("escaped char", "Unexpected 'a'", { currentLine = "a"
                                             line = 0
                                             column = 0 })

jUnicodeChar¶

In [28]:
let jUnicodeChar =
    let backslash = pchar '\\'
    let uChar = pchar 'u'
    let hexdigit = anyOf ([ '0' .. '9' ] @ [ 'A' .. 'F' ] @ [ 'a' .. 'f' ])
    let fourHexDigits = hexdigit .>>. hexdigit .>>. hexdigit .>>. hexdigit

    let inline convertToChar (((h1, h2), h3), h4) =
        let str = $"%c{h1}%c{h2}%c{h3}%c{h4}"
        Int32.Parse (str, Globalization.NumberStyles.HexNumber) |> char

    backslash >>. uChar >>. fourHexDigits
    |>> convertToChar
In [29]:
///- --test

run jUnicodeChar "\\u263A"
|> parserEqual (Success '☺')
'☺'
'☺'
Out[29]:
Success ('☺', { lines = [|"\u263A"|]
                position = { line = 0
                             column = 6 } })

jString¶

In [30]:
let quotedString =
    let quote = pchar '\"' <?> "quote"
    let jchar = jUnescapedChar <|> jEscapedChar <|> jUnicodeChar

    quote >>. manyChars jchar .>> quote
In [31]:
let jString =
    quotedString
    |>> JString
    <?> "quoted string"
In [32]:
///- --test

jValueRef <|
    choice
        [
            jNull
            jBool
            jString
        ]
In [33]:
///- --test

run jString "\"\""
|> parserEqual (Success (JString ""))
JString ""
JString ""
Out[33]:
Success (JString "", { lines = [|""""|]
                       position = { line = 0
                                    column = 2 } })
In [34]:
///- --test

run jString "\"a\""
|> parserEqual (Success (JString "a"))
JString "a"
JString "a"
Out[34]:
Success (JString "a", { lines = [|""a""|]
                        position = { line = 0
                                     column = 3 } })
In [35]:
///- --test

run jString "\"ab\""
|> parserEqual (Success (JString "ab"))
JString "ab"
JString "ab"
Out[35]:
Success (JString "ab", { lines = [|""ab""|]
                         position = { line = 0
                                      column = 4 } })
In [36]:
///- --test

run jString "\"ab\\tde\""
|> parserEqual (Success (JString "ab\tde"))
JString "ab	de"
JString "ab	de"
Out[36]:
Success (JString "ab	de", { lines = [|""ab\tde""|]
                            position = { line = 0
                                         column = 8 } })
In [37]:
///- --test

run jString "\"ab\\u263Ade\""
|> parserEqual (Success (JString "ab☺de"))
JString "ab☺de"
JString "ab☺de"
Out[37]:
Success (JString "ab☺de", { lines = [|""ab\u263Ade""|]
                            position = { line = 0
                                         column = 12 } })

jNumber¶

In [38]:
let jNumber =
    let optSign = opt (pchar '-')

    let zero = pstring "0"

    let digitOneNine =
        satisfy (fun ch -> Char.IsDigit ch && ch <> '0') "1-9"

    let digit =
        satisfy Char.IsDigit "digit"

    let point = pchar '.'

    let e = pchar 'e' <|> pchar 'E'

    let optPlusMinus = opt (pchar '-' <|> pchar '+')

    let nonZeroInt =
        digitOneNine .>>. manyChars digit
        |>> fun (first, rest) -> string first + rest

    let intPart = zero <|> nonZeroInt

    let fractionPart = point >>. manyChars1 digit

    let exponentPart = e >>. optPlusMinus .>>. manyChars1 digit

    let inline (|>?) opt f =
        match opt with
        | None -> ""
        | Some x -> f x

    let inline convertToJNumber (((optSign, intPart), fractionPart), expPart) =
        let signStr =
            optSign
            |>? string

        let fractionPartStr =
            fractionPart
            |>? (fun digits -> "." + digits)

        let expPartStr =
            expPart
            |>? fun (optSign, digits) ->
                let sign = optSign |>? string
                "e" + sign + digits

        (signStr + intPart + fractionPartStr + expPartStr)
        |> float
        |> JNumber

    optSign .>>. intPart .>>. opt fractionPart .>>. opt exponentPart
    |>> convertToJNumber
    <?> "number"
In [39]:
///- --test

jValueRef <|
    choice
        [
            jNull
            jBool
            jString
            jNumber
        ]
In [40]:
///- --test

run jNumber "123"
|> parserEqual (Success (JNumber 123.0))
JNumber 123.0
JNumber 123.0
Out[40]:
Success (JNumber 123.0, { lines = [|"123"|]
                          position = { line = 0
                                       column = 3 } })
In [41]:
///- --test

run jNumber "-123"
|> parserEqual (Success (JNumber -123.0))
JNumber -123.0
JNumber -123.0
Out[41]:
Success (JNumber -123.0, { lines = [|"-123"|]
                           position = { line = 0
                                        column = 4 } })
In [42]:
///- --test

run jNumber "123.4"
|> parserEqual (Success (JNumber 123.4))
JNumber 123.4
JNumber 123.4
Out[42]:
Success (JNumber 123.4, { lines = [|"123.4"|]
                          position = { line = 0
                                       column = 5 } })
In [43]:
///- --test

run jNumber "-123."
|> parserEqual (Success (JNumber -123.0))
JNumber -123.0
JNumber -123.0
Out[43]:
Success (JNumber -123.0, { lines = [|"-123."|]
                           position = { line = 0
                                        column = 4 } })
In [44]:
///- --test

run jNumber "00.1"
|> parserEqual (Success (JNumber 0.0))
JNumber 0.0
JNumber 0.0
Out[44]:
Success (JNumber 0.0, { lines = [|"00.1"|]
                        position = { line = 0
                                     column = 1 } })
In [45]:
///- --test

let jNumber_ = jNumber .>> spaces1
In [46]:
///- --test

run jNumber_ "123"
|> parserEqual (Success (JNumber 123.0))
JNumber 123.0
JNumber 123.0
Out[46]:
Success (JNumber 123.0, { lines = [|"123"|]
                          position = { line = 1
                                       column = 0 } })
In [47]:
///- --test

run jNumber_ "-123"
|> parserEqual (Success (JNumber -123.0))
JNumber -123.0
JNumber -123.0
Out[47]:
Success (JNumber -123.0, { lines = [|"-123"|]
                           position = { line = 1
                                        column = 0 } })
In [48]:
///- --test

run jNumber_ "-123."
|> parserEqual (
    Failure (
        "number andThen many1 whitespace",
        "Unexpected '.'",
        { currentLine = "-123."; line = 0; column = 4 }
    )
)
Line:0 Col:4 Error parsing number andThen many1 whitespace
-123.
    ^Unexpected '.'
Out[48]:
Failure ("number andThen many1 whitespace", "Unexpected '.'", { currentLine = "-123."
                                                                line = 0
                                                                column = 4 })
In [49]:
///- --test

run jNumber_ "123.4"
|> parserEqual (Success (JNumber 123.4))
JNumber 123.4
JNumber 123.4
Out[49]:
Success (JNumber 123.4, { lines = [|"123.4"|]
                          position = { line = 1
                                       column = 0 } })
In [50]:
///- --test

run jNumber_ "00.4"
|> parserEqual (
    Failure (
        "number andThen many1 whitespace",
        "Unexpected '0'",
        { currentLine = "00.4"; line = 0; column = 1 }
    )
)
Line:0 Col:1 Error parsing number andThen many1 whitespace
00.4
 ^Unexpected '0'
Out[50]:
Failure ("number andThen many1 whitespace", "Unexpected '0'", { currentLine = "00.4"
                                                                line = 0
                                                                column = 1 })
In [51]:
///- --test

run jNumber_ "123e4"
|> parserEqual (Success (JNumber 1230000.0))
JNumber 1230000.0
JNumber 1230000.0
Out[51]:
Success (JNumber 1230000.0, { lines = [|"123e4"|]
                              position = { line = 1
                                           column = 0 } })
In [52]:
///- --test

run jNumber_ "123.4e5"
|> parserEqual (Success (JNumber 12340000.0))
JNumber 12340000.0
JNumber 12340000.0
Out[52]:
Success (JNumber 12340000.0, { lines = [|"123.4e5"|]
                               position = { line = 1
                                            column = 0 } })
In [53]:
///- --test

run jNumber_ "123.4e-5"
|> parserEqual (Success (JNumber 0.001234))
JNumber 0.001234
JNumber 0.001234
Out[53]:
Success (JNumber 0.001234, { lines = [|"123.4e-5"|]
                             position = { line = 1
                                          column = 0 } })

jArray¶

In [54]:
let jArray =
    let left = pchar '[' .>> spaces
    let right = pchar ']' .>> spaces
    let comma = pchar ',' .>> spaces
    let value = jValue .>> spaces

    let values = sepBy value comma

    between left values right
    |>> JArray
    <?> "array"
In [55]:
///- --test

jValueRef <|
    choice
        [
            jNull
            jBool
            jString
            jNumber
            jArray
        ]
In [56]:
///- --test

run jArray "[ 1, 2 ]"
|> parserEqual (Success (JArray [ JNumber 1.0; JNumber 2.0 ]))
JArray [JNumber 1.0; JNumber 2.0]
JArray [JNumber 1.0; JNumber 2.0]
Out[56]:
Success (JArray [JNumber 1.0; JNumber 2.0], { lines = [|"[ 1, 2 ]"|]
                                              position = { line = 1
                                                           column = 0 } })
In [57]:
///- --test

run jArray "[ 1, 2, ]"
|> parserEqual (
    Failure (
        "array",
        "Unexpected ','",
        { currentLine = "[ 1, 2, ]"; line = 0; column = 6 }
    )
)
Line:0 Col:6 Error parsing array
[ 1, 2, ]
      ^Unexpected ','
Out[57]:
Failure ("array", "Unexpected ','", { currentLine = "[ 1, 2, ]"
                                      line = 0
                                      column = 6 })

jObject¶

In [58]:
let jObject =
    let left = spaces >>. pchar '{' .>> spaces
    let right = pchar '}' .>> spaces
    let colon = pchar ':' .>> spaces
    let comma = pchar ',' .>> spaces
    let key = quotedString .>> spaces
    let value = jValue .>> spaces

    let keyValue = (key .>> colon) .>>. value
    let keyValues = sepBy keyValue comma

    between left keyValues right
    |>> Map.ofList
    |>> JObject
    <?> "object"
In [59]:
jValueRef <|
    choice
        [
            jNull
            jBool
            jString
            jNumber
            jArray
            jObject
        ]
In [60]:
///- --test

run jObject """{ "a":1, "b"  :  2 }"""
|> parserEqual (
    Success (
        JObject (
            Map.ofList [
                "a", JNumber 1.0
                "b", JNumber 2.0
            ]
        )
    )
)
JObject (map [("a", JNumber 1.0); ("b", JNumber 2.0)])
JObject (map [("a", JNumber 1.0); ("b", JNumber 2.0)])
Out[60]:
Success (JObject (map [("a", JNumber 1.0); ("b", JNumber 2.0)]), { lines = [|"{ "a":1, "b"  :  2 }"|]
                                                                   position = { line = 1
                                                                                column = 0 } })
In [61]:
///- --test

run jObject """{ "a":1, "b"  :  2, }"""
|> parserEqual (
    Failure (
        "object",
        "Unexpected ','",
        { currentLine = """{ "a":1, "b"  :  2, }"""; line = 0; column = 18 }
    )
)
Line:0 Col:18 Error parsing object
{ "a":1, "b"  :  2, }
                  ^Unexpected ','
Out[61]:
Failure ("object", "Unexpected ','", { currentLine = "{ "a":1, "b"  :  2, }"
                                       line = 0
                                       column = 18 })

jValue¶

In [62]:
///- --test

let example1 = """{
    "name" : "Scott",
    "isMale" : true,
    "bday" : {"year":2001, "month":12, "day":25 },
    "favouriteColors" : ["blue", "green"],
    "emptyArray" : [],
    "emptyObject" : {}
}"""
run jValue example1
|> parserEqual (
    Success (
        JObject (
            Map.ofList [
                "name", JString "Scott"
                "isMale", JBool true
                "bday", JObject (
                    Map.ofList [
                        "year", JNumber 2001.0
                        "month", JNumber 12.0
                        "day", JNumber 25.0
                    ]
                )
                "favouriteColors", JArray [ JString "blue"; JString "green" ]
                "emptyArray", JArray []
                "emptyObject", JObject Map.empty
            ]
        )
    )
)
JObject
  (map
     [("bday",
       JObject
         (map
            [("day", JNumber 25.0); ("month", JNumber 12.0);
             ("year", JNumber 2001.0)])); ("emptyArray", JArray []);
      ("emptyObject", JObject (map []));
      ("favouriteColors", JArray [JString "blue"; JString "green"]);
      ("isMale", JBool true); ("name", JString "Scott")])
JObject
  (map
     [("bday", JObject (map [("day", JNumber 25.0); ("month", JNumber 12.0); ("year", JNumber 2001.0)]));
      ("emptyArray", JArray []); ("emptyObject", JObject (map []));
      ("favouriteColors", JArray [JString "blue"; JString "green"]); ("isMale", JBool true); ("name", JString "Scott")])
Out[62]:
Success
  (JObject
     (map
        [("bday", JObject (map [("day", JNumber 25.0); ("month", JNumber 12.0); ("year", JNumber 2001.0)]));
         ("emptyArray", JArray []); ("emptyObject", JObject (map []));
         ("favouriteColors", JArray [JString "blue"; JString "green"]); ("isMale", JBool true);
         ("name", JString "Scott")]),
   { lines =
      [|"{"; "    "name" : "Scott","; "    "isMale" : true,"; "    "bday" : {"year":2001, "month":12, "day":25 },";
        "    "favouriteColors" : ["blue", "green"],"; "    "emptyArray" : [],"; "    "emptyObject" : {}"; "}"|]
     position = { line = 8
                  column = 0 } })
In [63]:
///- --test

let example2 = """{"widget": {
    "debug": "on",
    "window": {
        "title": "Sample Konfabulator Widget",
        "name": "main_window",
        "width": 500,
        "height": 500
    },
    "image": {
        "src": "Images/Sun.png",
        "name": "sun1",
        "hOffset": 250,
        "vOffset": 250,
        "alignment": "center"
    },
    "text": {
        "data": "Click Here",
        "size": 36,
        "style": "bold",
        "name": "text1",
        "hOffset": 250,
        "vOffset": 100,
        "alignment": "center",
        "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;"
    }
}}"""

run jValue example2
|> parserEqual (
    Success (
        JObject (
            Map.ofList [
                "widget", JObject (
                    Map.ofList [
                        "debug", JString "on"
                        "window", JObject (
                            Map.ofList [
                                "title", JString "Sample Konfabulator Widget"
                                "name", JString "main_window"
                                "width", JNumber 500.0
                                "height", JNumber 500.0
                            ]
                        )
                        "image", JObject (
                            Map.ofList [
                                "src", JString "Images/Sun.png"
                                "name", JString "sun1"
                                "hOffset", JNumber 250.0
                                "vOffset", JNumber 250.0
                                "alignment", JString "center"
                            ]
                        )
                        "text", JObject (
                            Map.ofList [
                                "data", JString "Click Here"
                                "size", JNumber 36.0
                                "style", JString "bold"
                                "name", JString "text1"
                                "hOffset", JNumber 250.0
                                "vOffset", JNumber 100.0
                                "alignment", JString "center"
                                "onMouseUp", JString "sun1.opacity = (sun1.opacity / 100) * 90;"
                            ]
                        )
                    ]
                )
            ]
        )
    )
)

/home/runner/work/polyglot/polyglot/apps/parser/input.fsx(34,24): info FS3886: This list expression contains a single tuple element. Did you mean to use ';' instead of ',' to separate list elements?

JObject
  (map
     [("widget",
       JObject
         (map
            [("debug", JString "on");
             ("image",
              JObject
                (map
                   [("alignment", JString "center"); ("hOffset", JNumber 250.0);
                    ("name", JString "sun1"); ("src", JString "Images/Sun.png");
                    ("vOffset", JNumber 250.0)]));
             ("text",
              JObject
                (map
                   [("alignment", JString "center");
                    ("data", JString "Click Here"); ("hOffset", JNumber 250.0);
                    ("name", JString "text1");
                    ("onMouseUp",
                     JString "sun1.opacity = (sun1.opacity / 100) * 90;");
                    ("size", JNumber 36.0); ("style", JString "bold");
                    ("vOffset", JNumber 100.0)]));
             ("window",
              JObject
                (map
                   [("height", JNumber 500.0); ("name", JString "main_window");
                    ("title", JString "Sample Konfabulator Widget");
                    ("width", JNumber 500.0)]))]))])
JObject
  (map
     [("widget",
       JObject
         (map
            [("debug", JString "on");
             ("image",
              JObject
                (map
                   [("alignment", JString "center"); ("hOffset", JNumber 250.0); ("name", JString "sun1");
                    ("src", JString "Images/Sun.png"); ("vOffset", JNumber 250.0)]));
             ("text",
              JObject
                (map
                   [("alignment", JString "center"); ("data", JString "Click Here"); ("hOffset", JNumber 250.0);
                    ("name", JString "text1"); ("onMouseUp", JString "sun1.opacity = (sun1.opacity / 100) * 90;");
                    ("size", JNumber 36.0); ("style", JString "bold"); ("vOffset", JNumber 100.0)]));
             ("window",
              JObject
                (map
                   [("height", JNumber 500.0); ("name", JString "main_window");
                    ("title", JString "Sample Konfabulator Widget"); ("width", JNumber 500.0)]))]))])
Out[63]:
Success
  (JObject
     (map
        [("widget",
          JObject
            (map
               [("debug", JString "on");
                ("image",
                 JObject
                   (map
                      [("alignment", JString "center"); ("hOffset", JNumber 250.0); ("name", JString "sun1");
                       ("src", JString "Images/Sun.png"); ("vOffset", JNumber 250.0)]));
                ("text",
                 JObject
                   (map
                      [("alignment", JString "center"); ("data", JString "Click Here"); ("hOffset", JNumber 250.0);
                       ("name", JString "text1"); ("onMouseUp", JString "sun1.opacity = (sun1.opacity / 100) * 90;");
                       ("size", JNumber 36.0); ("style", JString "bold"); ("vOffset", JNumber 100.0)]));
                ("window",
                 JObject
                   (map
                      [("height", JNumber 500.0); ("name", JString "main_window");
                       ("title", JString "Sample Konfabulator Widget"); ("width", JNumber 500.0)]))]))]),
   { lines =
      [|"{"widget": {"; "    "debug": "on","; "    "window": {"; "        "title": "Sample Konfabulator Widget",";
        "        "name": "main_window","; "        "width": 500,"; "        "height": 500"; "    },"; "    "image": {";
        "        "src": "Images/Sun.png","; "        "name": "sun1","; "        "hOffset": 250,";
        "        "vOffset": 250,"; "        "alignment": "center""; "    },"; "    "text": {";
        "        "data": "Click Here","; "        "size": 36,"; "        "style": "bold","; "        "name": "text1",";
        "        "hOffset": 250,"; "        "vOffset": 100,"; "        "alignment": "center",";
        "        "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;""; "    }"; "}}"|]
     position = { line = 26
                  column = 0 } })
In [64]:
///- --test

let example3 = """{
  "string": "Hello, \"World\"!",
  "escapedString": "This string contains \\/\\\\\\b\\f\\n\\r\\t\\\"\\'",
  "number": 42,
  "scientificNumber": 3.14e-10,
  "boolean": true,
  "nullValue": null,
  "array": [1, 2, 3, 4, 5],
  "unicodeString1": "프리마",
  "unicodeString2": "\u0048\u0065\u006C\u006C\u006F, \u0022\u0057\u006F\u0072\u006C\u0064\u0022!",
  "specialCharacters": "!@#$%^&*()",
  "emptyArray": [],
  "emptyObject": {},
  "nestedArrays": [[1, 2, 3], [4, 5, 6]],
  "object": {
    "nestedString": "Nested Value",
    "nestedNumber": 3.14,
    "nestedBoolean": false,
    "nestedNull": null,
    "nestedArray": ["a", "b", "c"],
    "nestedObject": {
      "nestedProperty": "Nested Object Value"
    }
  },
  "nestedObjects": [
    {"name": "Alice", "age": 25},
    {"name": "Bob", "age": 30}
  ]
}"""
run jValue example3
|> parserEqual (
    Success (
        JObject (
            Map.ofList [
                "string", JString @"Hello, ""World""!"
                "escapedString", JString @"This string contains \/\\\b\f\n\r\t\""\'"
                "number", JNumber 42.0
                "scientificNumber", JNumber 3.14e-10
                "boolean", JBool true
                "nullValue", JNull
                "array", JArray [
                    JNumber 1.0; JNumber 2.0; JNumber 3.0; JNumber 4.0; JNumber 5.0
                ]
                "unicodeString1", JString "프리마"
                "unicodeString2", JString @"Hello, ""World""!"
                "specialCharacters", JString "!@#$%^&*()"
                "emptyArray", JArray []
                "emptyObject", JObject Map.empty
                "nestedArrays", JArray [
                    JArray [ JNumber 1.0; JNumber 2.0; JNumber 3.0 ]
                    JArray [ JNumber 4.0; JNumber 5.0; JNumber 6.0 ]
                ]
                "object", JObject (
                    Map.ofList [
                        "nestedString", JString "Nested Value"
                        "nestedNumber", JNumber 3.14
                        "nestedBoolean", JBool false
                        "nestedNull", JNull
                        "nestedArray", JArray [JString "a"; JString "b"; JString "c"]
                        "nestedObject", JObject (
                            Map.ofList [
                                "nestedProperty", JString "Nested Object Value"
                            ]
                        )
                    ]
                )
                "nestedObjects", JArray [
                  JObject (Map.ofList [ "name", JString "Alice"; "age", JNumber 25.0 ])
                  JObject (Map.ofList [ "name", JString "Bob"; "age", JNumber 30.0 ])
                ]
            ]
        )
    )
)

/home/runner/work/polyglot/polyglot/apps/parser/input.fsx(63,40): info FS3886: This list expression contains a single tuple element. Did you mean to use ';' instead of ',' to separate list elements?

JObject
  (map
     [("array",
       JArray [JNumber 1.0; JNumber 2.0; JNumber 3.0; JNumber 4.0; JNumber 5.0]);
      ("boolean", JBool true); ("emptyArray", JArray []);
      ("emptyObject", JObject (map []));
      ("escapedString", JString "This string contains \/\\\b\f\n\r\t\"\'");
      ("nestedArrays",
       JArray
         [JArray [JNumber 1.0; JNumber 2.0; JNumber 3.0];
          JArray [JNumber 4.0; JNumber 5.0; JNumber 6.0]]);
      ("nestedObjects",
       JArray
         [JObject (map [("age", JNumber 25.0); ("name", JString "Alice")]);
          JObject (map [("age", JNumber 30.0); ("name", JString "Bob")])]);
      ("nullValue", JNull); ("number", JNumber 42.0); ...])
JObject
  (map
     [("array", JArray [JNumber 1.0; JNumber 2.0; JNumber 3.0; JNumber 4.0; JNumber 5.0]); ("boolean", JBool true);
      ("emptyArray", JArray []); ("emptyObject", JObject (map []));
      ("escapedString", JString "This string contains \/\\\b\f\n\r\t\"\'");
      ("nestedArrays",
       JArray [JArray [JNumber 1.0; JNumber 2.0; JNumber 3.0]; JArray [JNumber 4.0; JNumber 5.0; JNumber 6.0]]);
      ("nestedObjects",
       JArray
         [JObject (map [("age", JNumber 25.0); ("name", JString "Alice")]);
          JObject (map [("age", JNumber 30.0); ("name", JString "Bob")])]); ("nullValue", JNull);
      ("number", JNumber 42.0); ...])
Out[64]:
Success
  (JObject
     (map
        [("array", JArray [JNumber 1.0; JNumber 2.0; JNumber 3.0; JNumber 4.0; JNumber 5.0]); ("boolean", JBool true);
         ("emptyArray", JArray []); ("emptyObject", JObject (map []));
         ("escapedString", JString "This string contains \/\\\b\f\n\r\t\"\'");
         ("nestedArrays",
          JArray [JArray [JNumber 1.0; JNumber 2.0; JNumber 3.0]; JArray [JNumber 4.0; JNumber 5.0; JNumber 6.0]]);
         ("nestedObjects",
          JArray
            [JObject (map [("age", JNumber 25.0); ("name", JString "Alice")]);
             JObject (map [("age", JNumber 30.0); ("name", JString "Bob")])]); ("nullValue", JNull);
         ("number", JNumber 42.0); ...]),
   { lines =
      [|"{"; "  "string": "Hello, \"World\"!",";
        "  "escapedString": "This string contains \\/\\\\\\b\\f\\n\\r\\t\\\"\\'","; "  "number": 42,";
        "  "scientificNumber": 3.14e-10,"; "  "boolean": true,"; "  "nullValue": null,"; "  "array": [1, 2, 3, 4, 5],";
        "  "unicodeString1": "프리마",";
        "  "unicodeString2": "\u0048\u0065\u006C\u006C\u006F, \u0022\u0057\u006F\u0072\u006C\u0064\u0022!",";
        "  "specialCharacters": "!@#$%^&*()","; "  "emptyArray": [],"; "  "emptyObject": {},";
        "  "nestedArrays": [[1, 2, 3], [4, 5, 6]],"; "  "object": {"; "    "nestedString": "Nested Value",";
        "    "nestedNumber": 3.14,"; "    "nestedBoolean": false,"; "    "nestedNull": null,";
        "    "nestedArray": ["a", "b", "c"],"; "    "nestedObject": {"; "      "nestedProperty": "Nested Object Value"";
        "    }"; "  },"; "  "nestedObjects": ["; "    {"name": "Alice", "age": 25},"; "    {"name": "Bob", "age": 30}";
        "  ]"; "}"|]
     position = { line = 29
                  column = 0 } })