From 6358bb3652bf6fd486556cd7f0f5df5591caafa3 Mon Sep 17 00:00:00 2001 From: Eugene Baranov <220614+eugbaranov@users.noreply.github.com> Date: Tue, 18 Jun 2019 22:01:21 +0100 Subject: [PATCH 1/4] Add test for parsing deep arrays --- tests/FSharp.Data.Tests/JsonValue.fs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/FSharp.Data.Tests/JsonValue.fs b/tests/FSharp.Data.Tests/JsonValue.fs index bcb82aa17..009455460 100644 --- a/tests/FSharp.Data.Tests/JsonValue.fs +++ b/tests/FSharp.Data.Tests/JsonValue.fs @@ -95,6 +95,12 @@ let ``Can parse document with datetime offset from iso date format``() = let j = JsonValue.Parse "{\"anniversary\": \"2009-05-19 14:39:22+0600\"}" j?anniversary.AsDateTimeOffset() |> should equal <| DateTimeOffset(2009, 05, 19, 14, 39, 22, TimeSpan.FromHours 6.) +[] +let ``Can parse deep arrays``() = + String.replicate 50000 "[" + String.replicate 50000 "]" + |> FSharp.Data.JsonValue.Parse + |> ignore + // TODO: Due to limitations in the current ISO 8601 datetime parsing these fail, and should be made to pass //[] //let ``Cant Yet parse document with basic iso date``() = From 91cae9da46bc5e49b4eb88d16a495012417cfdda Mon Sep 17 00:00:00 2001 From: Eugene Baranov <220614+eugbaranov@users.noreply.github.com> Date: Tue, 18 Jun 2019 22:01:42 +0100 Subject: [PATCH 2/4] Rewrite parseArray in CPS (closes #1180) --- src/Json/JsonValue.fs | 54 +++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/src/Json/JsonValue.fs b/src/Json/JsonValue.fs index df3ae0cb4..3c3a936be 100644 --- a/src/Json/JsonValue.fs +++ b/src/Json/JsonValue.fs @@ -167,18 +167,18 @@ type private JsonParser(jsonText:string) = if not cond then throw() // Recursive descent parser for JSON that uses global mutable index - let rec parseValue() = + let rec parseValue cont = skipWhitespace() ensure(i < s.Length) match s.[i] with - | '"' -> JsonValue.String(parseString()) - | '-' -> parseNum() - | c when Char.IsDigit(c) -> parseNum() - | '{' -> parseObject() - | '[' -> parseArray() - | 't' -> parseLiteral("true", JsonValue.Boolean true) - | 'f' -> parseLiteral("false", JsonValue.Boolean false) - | 'n' -> parseLiteral("null", JsonValue.Null) + | '"' -> JsonValue.String(parseString()) |> cont + | '-' -> parseNum() |> cont + | c when Char.IsDigit(c) -> parseNum() |> cont + | '{' -> parseObject() |> cont + | '[' -> parseArray cont + | 't' -> parseLiteral("true", JsonValue.Boolean true) |> cont + | 'f' -> parseLiteral("false", JsonValue.Boolean false) |> cont + | 'n' -> parseLiteral("null", JsonValue.Null) |> cont | _ -> throw() and parseString() = @@ -249,7 +249,7 @@ type private JsonParser(jsonText:string) = ensure(i < s.Length && s.[i] = ':') i <- i + 1 skipWhitespace() - key, parseValue() + key, parseValue id and parseObject() = ensure(i < s.Length && s.[i] = '{') @@ -268,22 +268,32 @@ type private JsonParser(jsonText:string) = i <- i + 1 JsonValue.Record(pairs.ToArray()) - and parseArray() = + and parseArray cont = ensure(i < s.Length && s.[i] = '[') i <- i + 1 skipWhitespace() let vals = ResizeArray<_>() + let parseArrayEnd() = + ensure(i < s.Length && s.[i] = ']') + i <- i + 1 + vals.ToArray() |> JsonValue.Array |> cont if i < s.Length && s.[i] <> ']' then - vals.Add(parseValue()) - skipWhitespace() - while i < s.Length && s.[i] = ',' do - i <- i + 1 - skipWhitespace() - vals.Add(parseValue()) + parseValue (fun v -> + vals.Add(v) skipWhitespace() - ensure(i < s.Length && s.[i] = ']') - i <- i + 1 - JsonValue.Array(vals.ToArray()) + let rec parseArrayItem() = + if i < s.Length && s.[i] = ',' then + i <- i + 1 + skipWhitespace() + parseValue (fun v -> + vals.Add(v) + skipWhitespace() + parseArrayItem()) + else + parseArrayEnd() + parseArrayItem()) + else + parseArrayEnd() and parseLiteral(expected, r) = ensure(i+expected.Length <= s.Length) @@ -294,7 +304,7 @@ type private JsonParser(jsonText:string) = // Start by parsing the top-level value member x.Parse() = - let value = parseValue() + let value = parseValue id skipWhitespace() if i <> s.Length then throw() @@ -303,7 +313,7 @@ type private JsonParser(jsonText:string) = member x.ParseMultiple() = seq { while i <> s.Length do - yield parseValue() + yield parseValue id skipWhitespace() } From f5553ce6481339e377d76e55d962c12672d206c2 Mon Sep 17 00:00:00 2001 From: Eugene Baranov <220614+eugbaranov@users.noreply.github.com> Date: Tue, 18 Jun 2019 22:02:18 +0100 Subject: [PATCH 3/4] Add test for parsing deep objects --- tests/FSharp.Data.Tests/JsonValue.fs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/FSharp.Data.Tests/JsonValue.fs b/tests/FSharp.Data.Tests/JsonValue.fs index 009455460..1c47128be 100644 --- a/tests/FSharp.Data.Tests/JsonValue.fs +++ b/tests/FSharp.Data.Tests/JsonValue.fs @@ -101,6 +101,12 @@ let ``Can parse deep arrays``() = |> FSharp.Data.JsonValue.Parse |> ignore +[] +let ``Can parse deep objects``() = + (String.replicate 50000 "{\"a\":") + "\"\"" + (String.replicate 50000 "}") + |> FSharp.Data.JsonValue.Parse + |> ignore + // TODO: Due to limitations in the current ISO 8601 datetime parsing these fail, and should be made to pass //[] //let ``Cant Yet parse document with basic iso date``() = From 2ed8ec81b9e76545f61259fab7194c4a76fa4359 Mon Sep 17 00:00:00 2001 From: Eugene Baranov <220614+eugbaranov@users.noreply.github.com> Date: Tue, 18 Jun 2019 22:03:00 +0100 Subject: [PATCH 4/4] Rewrite parseObject in CPS --- src/Json/JsonValue.fs | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/src/Json/JsonValue.fs b/src/Json/JsonValue.fs index 3c3a936be..7c12103fd 100644 --- a/src/Json/JsonValue.fs +++ b/src/Json/JsonValue.fs @@ -174,7 +174,7 @@ type private JsonParser(jsonText:string) = | '"' -> JsonValue.String(parseString()) |> cont | '-' -> parseNum() |> cont | c when Char.IsDigit(c) -> parseNum() |> cont - | '{' -> parseObject() |> cont + | '{' -> parseObject cont | '[' -> parseArray cont | 't' -> parseLiteral("true", JsonValue.Boolean true) |> cont | 'f' -> parseLiteral("false", JsonValue.Boolean false) |> cont @@ -243,30 +243,40 @@ type private JsonParser(jsonText:string) = | Some x -> JsonValue.Float x | _ -> throw() - and parsePair() = + and parsePair cont = let key = parseString() skipWhitespace() ensure(i < s.Length && s.[i] = ':') i <- i + 1 skipWhitespace() - key, parseValue id + parseValue (fun v -> (key, v) |> cont) - and parseObject() = + and parseObject cont = ensure(i < s.Length && s.[i] = '{') i <- i + 1 skipWhitespace() let pairs = ResizeArray<_>() + let parseObjectEnd() = + ensure(i < s.Length && s.[i] = '}') + i <- i + 1 + pairs.ToArray() |> JsonValue.Record |> cont if i < s.Length && s.[i] = '"' then - pairs.Add(parsePair()) - skipWhitespace() - while i < s.Length && s.[i] = ',' do - i <- i + 1 - skipWhitespace() - pairs.Add(parsePair()) + parsePair (fun p -> + pairs.Add(p) skipWhitespace() - ensure(i < s.Length && s.[i] = '}') - i <- i + 1 - JsonValue.Record(pairs.ToArray()) + let rec parsePairItem() = + if i < s.Length && s.[i] = ',' then + i <- i + 1 + skipWhitespace() + parsePair (fun p -> + pairs.Add(p) + skipWhitespace() + parsePairItem()) + else + parseObjectEnd() + parsePairItem()) + else + parseObjectEnd() and parseArray cont = ensure(i < s.Length && s.[i] = '[')