From e0127dd67001edf6523b20d6940c2e3699c2e6fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Mayer?= Date: Tue, 5 Dec 2023 16:56:46 -0600 Subject: [PATCH 1/4] Release process on Mac instead of Ubuntu (#4846) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hopefully fixes https://github.com/dafny-lang/dafny/issues/4838 ### Description ### How has this been tested? By submitting this pull request, I confirm that my contribution is made under the terms of the [MIT license](https://github.com/dafny-lang/dafny/blob/master/LICENSE.txt). --- .github/workflows/publish-release-reusable.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-release-reusable.yml b/.github/workflows/publish-release-reusable.yml index ccdc1c22c47..60ebf051cbf 100644 --- a/.github/workflows/publish-release-reusable.yml +++ b/.github/workflows/publish-release-reusable.yml @@ -35,7 +35,7 @@ env: jobs: publish-release: - runs-on: ubuntu-20.04 + runs-on: macos-latest # Put back 'ubuntu-20.04' if macos-latest fails in any way steps: - name: Print version run: echo ${{ inputs.name }} From 6a949637046e889196272ee708008af966e6ff6a Mon Sep 17 00:00:00 2001 From: Stefan Zetzsche <120379523+stefan-aws@users.noreply.github.com> Date: Wed, 6 Dec 2023 00:04:16 +0000 Subject: [PATCH 2/4] Stdlib Json (#4754) Co-authored-by: Robin Salkeld Co-authored-by: Remy Willems --- Source/DafnyCore/AST/Grammar/Printer.cs | 2 + .../DafnyCore/Rewriters/PrecedenceLinter.cs | 40 +- Source/DafnyStandardLibraries/Makefile | 4 +- Source/DafnyStandardLibraries/README.md | 13 +- .../DafnyStandardLibraries-arithmetic.doo | Bin 17788 -> 17788 bytes .../binaries/DafnyStandardLibraries-cs.doo | Bin 743 -> 743 bytes .../binaries/DafnyStandardLibraries-go.doo | Bin 769 -> 769 bytes .../binaries/DafnyStandardLibraries-java.doo | Bin 743 -> 743 bytes .../binaries/DafnyStandardLibraries-js.doo | Bin 743 -> 743 bytes .../DafnyStandardLibraries-notarget.doo | Bin 718 -> 718 bytes .../binaries/DafnyStandardLibraries-py.doo | Bin 737 -> 737 bytes .../binaries/DafnyStandardLibraries.doo | Bin 20244 -> 39028 bytes .../examples/JSON/JSONExamples.dfy | 273 +++++ .../examples/JSON/JSONTests.dfy | 137 +++ .../EnableNonLinearArithmetic/BoundedInts.dfy | 23 + .../Collections/Seqs.dfy | 27 +- .../EnableNonLinearArithmetic/JSON/API.dfy | 42 + .../JSON/ByteStrConversion.dfy | 25 + .../JSON/ConcreteSyntax.Spec.dfy | 130 +++ .../JSON/ConcreteSyntax.SpecProperties.dfy | 50 + .../JSON/Deserializer.dfy | 163 +++ .../EnableNonLinearArithmetic/JSON/Errors.dfy | 63 ++ .../JSON/Grammar.dfy | 99 ++ .../EnableNonLinearArithmetic/JSON/JSON.md | 111 ++ .../JSON/Serializer.dfy | 136 +++ .../EnableNonLinearArithmetic/JSON/Spec.dfy | 132 +++ .../JSON/Utils/Cursors.dfy | 340 ++++++ .../JSON/Utils/Lexers.dfy | 56 + .../JSON/Utils/Parsers.dfy | 64 ++ .../JSON/Utils/Views.Writers.dfy | 122 +++ .../JSON/Utils/Views.dfy | 127 +++ .../EnableNonLinearArithmetic/JSON/Values.dfy | 24 + .../JSON/ZeroCopy/API.dfy | 51 + .../JSON/ZeroCopy/Deserializer.dfy | 988 ++++++++++++++++++ .../JSON/ZeroCopy/Serializer.dfy | 550 ++++++++++ .../EnableNonLinearArithmetic/dfyconfig.toml | 3 + .../src/DafnyStdLibs/TargetSpecific/Makefile | 4 + 37 files changed, 3770 insertions(+), 29 deletions(-) create mode 100644 Source/DafnyStandardLibraries/examples/JSON/JSONExamples.dfy create mode 100644 Source/DafnyStandardLibraries/examples/JSON/JSONTests.dfy create mode 100644 Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/API.dfy create mode 100644 Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/ByteStrConversion.dfy create mode 100644 Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/ConcreteSyntax.Spec.dfy create mode 100644 Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/ConcreteSyntax.SpecProperties.dfy create mode 100644 Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Deserializer.dfy create mode 100644 Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Errors.dfy create mode 100644 Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Grammar.dfy create mode 100644 Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/JSON.md create mode 100644 Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Serializer.dfy create mode 100644 Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Spec.dfy create mode 100644 Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Utils/Cursors.dfy create mode 100644 Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Utils/Lexers.dfy create mode 100644 Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Utils/Parsers.dfy create mode 100644 Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Utils/Views.Writers.dfy create mode 100644 Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Utils/Views.dfy create mode 100644 Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Values.dfy create mode 100644 Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/ZeroCopy/API.dfy create mode 100644 Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/ZeroCopy/Deserializer.dfy create mode 100644 Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/ZeroCopy/Serializer.dfy diff --git a/Source/DafnyCore/AST/Grammar/Printer.cs b/Source/DafnyCore/AST/Grammar/Printer.cs index 3a121240f15..640d0b02db4 100644 --- a/Source/DafnyCore/AST/Grammar/Printer.cs +++ b/Source/DafnyCore/AST/Grammar/Printer.cs @@ -2690,6 +2690,8 @@ void PrintExpr(Expression expr, int contextBindingStrength, bool fragileContext, wr.Write("var "); PrintCasePattern(e.Lhs); wr.Write(" :- "); + } else { + wr.Write(":- "); } PrintExpression(e.Rhs, true); wr.Write("; "); diff --git a/Source/DafnyCore/Rewriters/PrecedenceLinter.cs b/Source/DafnyCore/Rewriters/PrecedenceLinter.cs index 8c13cc656fa..d656b8e6a06 100644 --- a/Source/DafnyCore/Rewriters/PrecedenceLinter.cs +++ b/Source/DafnyCore/Rewriters/PrecedenceLinter.cs @@ -17,7 +17,11 @@ namespace Microsoft.Dafny { public class PrecedenceLinter : IRewriter { private CompilationData compilation; + // Don't perform linting on doo files in general, since the source has already been processed. internal override void PostResolve(ModuleDefinition moduleDefinition) { + if (moduleDefinition.tok.Uri != null && moduleDefinition.tok.Uri.LocalPath.EndsWith(".doo")) { + return; + } foreach (var topLevelDecl in moduleDefinition.TopLevelDecls.OfType()) { foreach (var callable in topLevelDecl.Members.OfType()) { var visitor = new PrecedenceLinterVisitor(compilation, Reporter); @@ -107,27 +111,27 @@ protected override bool VisitOneExpr(Expression expr, ref LeftMargin st) { // that is, we inspect line and column information. if (expr is BinaryExpr bin && (bin.Op == BinaryExpr.Opcode.Imp || bin.Op == BinaryExpr.Opcode.Exp || bin.Op == BinaryExpr.Opcode.Iff)) { + // For + // a) LHS ==> RHS + // b) LHS ==> + // RHS-somewhere-on-this-line + // use LHS.StartToken as the left margin. + // For + // c) LHS0 && + // LHS1 ==> RHS + // use expr.tok (that is, the location of ==>) as the left margin. This is bound to generate a warning. + // For + // d) LHS0 && + // LHS1 ==> + // RHS-somewhere-on-this-line + // e) LHS0 && + // LHS1 + // ==> + // RHS-somewhere-on-this-line + // use LHS.StartToken as the left margin. VisitLhsComponent(expr.tok, bin.E0, - // For - // a) LHS ==> RHS - // b) LHS ==> - // RHS-somewhere-on-this-line - // use LHS.StartToken as the left margin. bin.E0.StartToken.line == expr.tok.line ? bin.E0.StartToken.col : - // For - // c) LHS0 && - // LHS1 ==> RHS - // use expr.tok (that is, the location of ==>) as the left margin. This is bound to generate a warning. bin.E1.StartToken.line == expr.tok.line ? expr.tok.col : - // For - // d) LHS0 && - // LHS1 ==> - // RHS-somewhere-on-this-line - // e) LHS0 && - // LHS1 - // ==> - // RHS-somewhere-on-this-line - // use LHS.StartToken as the left margin. bin.E0.StartToken.col, "left-hand operand of " + BinaryExpr.OpcodeString(bin.Op)); VisitRhsComponent(expr.tok, bin.E1, "right-hand operand of " + BinaryExpr.OpcodeString(bin.Op)); diff --git a/Source/DafnyStandardLibraries/Makefile b/Source/DafnyStandardLibraries/Makefile index 383b41562f3..b324854e7cc 100644 --- a/Source/DafnyStandardLibraries/Makefile +++ b/Source/DafnyStandardLibraries/Makefile @@ -11,8 +11,8 @@ DOO_FILE_ARITHMETIC_TARGET=binaries/DafnyStandardLibraries-arithmetic.doo all: check-binary test check-format check-examples verify: - $(DAFNY) verify src/dfyconfig.toml - $(DAFNY) verify src/dfyconfig-arithmetic.toml + $(DAFNY) verify src/DafnyStdLibs/EnableNonLinearArithmetic/dfyconfig.toml + $(DAFNY) verify src/DafnyStdLibs/DisableNonLinearArithmetic/dfyconfig.toml build-binary: $(DAFNY) build -t:lib src/DafnyStdLibs/EnableNonLinearArithmetic/dfyconfig.toml --output:${DOO_FILE_SOURCE} diff --git a/Source/DafnyStandardLibraries/README.md b/Source/DafnyStandardLibraries/README.md index dcb609dd54a..c016d2dca9e 100644 --- a/Source/DafnyStandardLibraries/README.md +++ b/Source/DafnyStandardLibraries/README.md @@ -45,13 +45,14 @@ In particular, `--standard-libraries` currently cannot be used together with `-- The sections below describe how to use each library: -- [DafnyStdLibs.BoundedInts](src/DafnyStdLibs/BoundedInts) -- definitions of types and constants for fixed-bit-width integers -- [DafnyStdLibs.Wrappers](src/DafnyStdLibs/Wrappers) -- simple datatypes to support common patterns, such as optional values or the result of operations that can fail -- [DafnyStdLibs.Relations](src/DafnyStdLibs/Relations) -- properties of relations -- [DafnyStdLibs.Functions](src/DafnyStdLibs/Functions) -- properties of functions -- [DafnyStdLibs.Collections](src/DafnyStdLibs/Collections) -- properties of the built-in collection types (seq, set, iset, map, imap, array) +- [DafnyStdLibs.BoundedInts](src/DafnyStdLibs/EnableNonLinearArithmetic/BoundedInts) -- definitions of types and constants for fixed-bit-width integers +- [DafnyStdLibs.Wrappers](src/DafnyStdLibs/EnableNonLinearArithmetic/Wrappers) -- simple datatypes to support common patterns, such as optional values or the result of operations that can fail +- [DafnyStdLibs.Relations](src/DafnyStdLibs/EnableNonLinearArithmetic/Relations) -- properties of relations +- [DafnyStdLibs.Functions](src/DafnyStdLibs/EnableNonLinearArithmetic/Functions) -- properties of functions +- [DafnyStdLibs.Collections](src/DafnyStdLibs/EnableNonLinearArithmetic/Collections) -- properties of the built-in collection types (seq, set, iset, map, imap, array) - DafnyStdLibs.DynamicArray -- an array that can grow and shrink -- [DafnyStdLibs.Base64](src/DafnyStdLibs/Base64) -- base-64 encoding and decoding +- [DafnyStdLibs.Base64](src/DafnyStdLibs/EnableNonLinearArithmetic/Base64) -- base-64 encoding and decoding +- [DafnyStdLibs.JSON](src/DafnyStdLibs/EnableNonLinearArithmetic/JSON) -- JSON serialization and deserialization We are in the process of importing many more libraries, in particular from the existing [`dafny-lang/libraries`](https://github.com/dafny-lang/libraries) GitHub repository. diff --git a/Source/DafnyStandardLibraries/binaries/DafnyStandardLibraries-arithmetic.doo b/Source/DafnyStandardLibraries/binaries/DafnyStandardLibraries-arithmetic.doo index c3e170af1ba899a1a29df95552376d40971deedc..5d238be60d43f3494851d502f1640e88eaf5419d 100644 GIT binary patch delta 47 vcmey<#rUU-kvG7bnMH(wfq{b|F}-yn?*(Qcwef+H3y7Y~>S_U|9b9byUp5Zq delta 47 vcmey<#rUU-kvG7bnMH(wfq{d8vAtj-?*(Qcwef+H3y7Y~>S_U|9b9byMt2R7 diff --git a/Source/DafnyStandardLibraries/binaries/DafnyStandardLibraries-cs.doo b/Source/DafnyStandardLibraries/binaries/DafnyStandardLibraries-cs.doo index 9a0567a67f691104b7e7e6a3ef6b34390af36d6f..86b99caa9a4766fbd8859aabb9c1cbd2a95704c4 100644 GIT binary patch delta 45 ucmaFP`ka+Fz?+#xgn@y9gCRM+bt3NtW+1ikK`$eSo?O6W0j5_o*#H16#11b2 delta 45 ucmaFP`ka+Fz?+#xgn@y9gMq2NU?T4YW+1ikK`$eSo?O6W0j5_o*#H0&x(wF< diff --git a/Source/DafnyStandardLibraries/binaries/DafnyStandardLibraries-go.doo b/Source/DafnyStandardLibraries/binaries/DafnyStandardLibraries-go.doo index 77846eeb8087aafb10dd136f2e445c818a8bfd5e..a4699842f9e30efc3c9e1dfb3bbda1f4b6870117 100644 GIT binary patch delta 45 tcmZodM@MdNaVPIh3U`S1GoydEE8Axq>u$&P@Pwrx}0MkdAYyb`54Z8pU delta 45 tcmZodM@MdNaVPIh3U|?-8n8u$&P@Pwrx}0MkdAYykBV3}*lU diff --git a/Source/DafnyStandardLibraries/binaries/DafnyStandardLibraries-java.doo b/Source/DafnyStandardLibraries/binaries/DafnyStandardLibraries-java.doo index f4f36b7058b07606d1bd96c3e861c4713909cc3a..6b5bbc02ae03a29053f98e36f35796920eaede44 100644 GIT binary patch delta 45 ucmaFP`ka+Fz?+#xgn@y9gCQlobt3NtW+1ikK`$eSo?O6W0j5_o*#H174-PW` delta 45 ucmaFP`ka+Fz?+#xgn@y9gMqodU?T4YW+1ikK`$eSo?O6W0j5_o*#H0(1q|B& diff --git a/Source/DafnyStandardLibraries/binaries/DafnyStandardLibraries-js.doo b/Source/DafnyStandardLibraries/binaries/DafnyStandardLibraries-js.doo index f9481470f086b55b1ae005ea46e93909666cd478..6b5bbc02ae03a29053f98e36f35796920eaede44 100644 GIT binary patch delta 45 ucmaFP`ka+Fz?+#xgn@y9gCQlobt3NtW+1ikK`$eSo?O6W0j5_o*#H174-PW` delta 45 ucmaFP`ka+Fz?+#xgn@y9gMp>JU?T4YW+1ikK`$eSo?O6W0j5_o*#H0(Pz>Jy diff --git a/Source/DafnyStandardLibraries/binaries/DafnyStandardLibraries-notarget.doo b/Source/DafnyStandardLibraries/binaries/DafnyStandardLibraries-notarget.doo index f2cae47966c0474c0ec16a219a10ce688629d229..1e6ab104b059c9bda199be0a0bddd391d1ae08fd 100644 GIT binary patch delta 45 ucmX@ddXAMhz?+#xgn@y9gCQxsbt3NtW+1ikK?x&>o*d3(0j4K0*#H0_!40ke delta 45 ucmX@ddXAMhz?+#xgn@y9gMqQVU?T4YW+1ikK?x&>o*d3(0j4K0*#H0sw+vPQ diff --git a/Source/DafnyStandardLibraries/binaries/DafnyStandardLibraries-py.doo b/Source/DafnyStandardLibraries/binaries/DafnyStandardLibraries-py.doo index 14644f270007d8a234c6ece2a58062cd489b3b15..796c659998c49dbe10a410504f402aedb792a04e 100644 GIT binary patch delta 45 ucmaFJ`jC}3z?+#xgn@y9gCQ-wbt3NtW+1ikK|3Reo}9&G0j8HS*#H14)eap1 delta 45 ucmaFJ`jC}3z?+#xgn@y9gMqcZU?T4YW+1ikK|3Reo}9&G0j8HS*#H0$%M8T; diff --git a/Source/DafnyStandardLibraries/binaries/DafnyStandardLibraries.doo b/Source/DafnyStandardLibraries/binaries/DafnyStandardLibraries.doo index 2baa12099431cb12950364ca3d24479d1032a9ca..70d618fd76738bd3dda56ba86da6c6ad1bde2ecc 100644 GIT binary patch literal 39028 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

S5qe(?fXdg=U`w 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 delta 20115 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 G0002$!)2%d diff --git a/Source/DafnyStandardLibraries/examples/JSON/JSONExamples.dfy b/Source/DafnyStandardLibraries/examples/JSON/JSONExamples.dfy new file mode 100644 index 00000000000..91c4d7dd829 --- /dev/null +++ b/Source/DafnyStandardLibraries/examples/JSON/JSONExamples.dfy @@ -0,0 +1,273 @@ +/** + This library offers two APIs: a high-level one (giving abstract value trees + with no concrete syntactic details) and a low-level one (including all + information about blanks, separator positions, character escapes, etc.). + */ + +/** High-level API (JSON values) */ +module {:options "-functionSyntax:4"} AbstractSyntax { + import DafnyStdLibs.JSON.API + import opened DafnyStdLibs.JSON.Values + import opened DafnyStdLibs.Wrappers + import opened DafnyStdLibs.Unicode.UnicodeStringsWithUnicodeChar + + /** + The high-level API works with fairly simple datatype values that contain + native Dafny strings. + */ + method {:test} {:rlimit 100000} Test() { + + /** + Use `API.Deserialize` to deserialize a byte string. + For example, here is how to decode the JSON test `"[true]"`. (We need to + convert from Dafny's native strings to byte strings because Dafny does not + have syntax for byte strings; in a real application, we would be reading and + writing raw bytes directly from disk or from the network instead). + */ + var SIMPLE_JS :- expect ToUTF8Checked("[true]"); + var SIMPLE_VALUE := Array([Bool(true)]); + expect API.Deserialize(SIMPLE_JS) == Success(SIMPLE_VALUE); + + /** + Here is a larger object, written using a verbatim string (with `@"`). + In verbatim strings `""` represents a single double-quote character): + */ + var CITIES_JS :- expect ToUTF8Checked(@"{ + ""Cities"": [ + { + ""Name"": ""Boston"", + ""Founded"": 1630, + ""Population"": 689386, + ""Area (km2)"": 4584.2 + }, { + ""Name"": ""Rome"", + ""Founded"": -753, + ""Population"": 2.873e6, + ""Area (km2)"": 1285 + }, { + ""Name"": ""Paris"", + ""Founded"": null, + ""Population"": 2.161e6, + ""Area (km2)"": 2383.5 + } + ] + }"); + var CITIES_VALUE := + Object([ + ("Cities", Array([ + Object([ + ("Name", String("Boston")), + ("Founded", Number(Int(1630))), + ("Population", Number(Int(689386))), + ("Area (km2)", Number(Decimal(45842, -1))) + ]), + Object([ + ("Name", String("Rome")), + ("Founded", Number(Int(-753))), + ("Population", Number(Decimal(2873, 3))), + ("Area (km2)", Number(Int(1285))) + ]), + Object([ + ("Name", String("Paris")), + ("Founded", Null), + ("Population", Number(Decimal(2161, 3))), + ("Area (km2)", Number(Decimal(23835, -1))) + ]) + ])) + ]); + expect API.Deserialize(CITIES_JS) == Success(CITIES_VALUE); + + /** + Serialization works similarly, with `API.Serialize`. For this first example + the generated string matches what we started with exactly: + */ + expect API.Serialize(SIMPLE_VALUE) == Success(SIMPLE_JS); + + /** + For more complex object, the generated layout may not be exactly the same; + note in particular how the representation of numbers and the whitespace have changed. + */ + var EXPECTED :- expect ToUTF8Checked( + @"{""Cities"":[{""Name"":""Boston"",""Founded"":1630,""Population"":689386,""Area (km2)"":45842e-1},{""Name"":""Rome"",""Founded"":-753,""Population"":2873e3,""Area (km2)"":1285},{""Name"":""Paris"",""Founded"":null,""Population"":2161e3,""Area (km2)"":23835e-1}]}" + ); + expect API.Serialize(CITIES_VALUE) == Success(EXPECTED); + + /** + Additional methods are defined in `API.dfy` to serialize an object into an + existing buffer or into an array. Below is the smaller example as a sanity check: + */ + var CITY_JS :- expect ToUTF8Checked(@"{""Cities"": [{ + ""Name"": ""Boston"", + ""Founded"": 1630, + ""Population"": 689386, + ""Area (km2)"": 4584.2}]}"); + + var CITY_VALUE := + Object([("Cities", Array([ + Object([ + ("Name", String("Boston")), + ("Founded", Number(Int(1630))), + ("Population", Number(Int(689386))), + ("Area (km2)", Number(Decimal(45842, -1)))])]))]); + + expect API.Deserialize(CITY_JS) == Success(CITY_VALUE); + + var EXPECTED' :- expect ToUTF8Checked( + @"{""Cities"":[{""Name"":""Boston"",""Founded"":1630,""Population"":689386,""Area (km2)"":45842e-1}]}" + ); + + expect API.Serialize(CITY_VALUE) == Success(EXPECTED'); + } +} + +/** Low-level API (concrete syntax) */ + +/** + If you care about low-level performance, or about preserving existing + formatting as much as possible, you may prefer to use the lower-level API: + */ +module {:options "-functionSyntax:4"} ConcreteSyntax { + import DafnyStdLibs.JSON.ZeroCopy.API + import opened DafnyStdLibs.Unicode.UnicodeStringsWithUnicodeChar + import opened DafnyStdLibs.JSON.Grammar + import opened DafnyStdLibs.Wrappers + import DafnyStdLibs.Collections.Seqs + + /** + The low-level API works with ASTs that record all details of formatting and + encoding: each node contains pointers to parts of a string, such that + concatenating the fields of all nodes reconstructs the serialized value. + */ + method {:test} {:rlimit 100000} Test() { + + /** + The low-level API exposes the same functions and methods as the high-level + one, but the type that they consume and produce is `Grammar.JSON` (defined + in `Grammar.dfy` as a `Grammar.Value` surrounded by optional whitespace) + instead of `Values.JSON` (defined in `Values.dfy`). Since `Grammar.JSON` contains + all formatting information, re-serializing an object produces the original value: + */ + var CITIES :- expect ToUTF8Checked(@"{ + ""Cities"": [ + { + ""Name"": ""Boston"", + ""Founded"": 1630, + ""Population"": 689386, + ""Area (km2)"": 4600 + }, { + ""Name"": ""Rome"", + ""Founded"": -753, + ""Population"": 2.873e6, + ""Area (km2)"": 1285 + }, { + ""Name"": ""Paris"", + ""Founded"": null, + ""Population"": 2.161e6, + ""Area (km2)"": 2383.5 + } + ] + }"); + + var deserialized :- expect API.Deserialize(CITIES); + expect API.Serialize(deserialized) == Success(CITIES); + + /** + Since the formatting is preserved, it is also possible to write + minimally-invasive transformations over an AST. For example, let's replace + `null` in the object above with `"Unknown"`. First, we construct a JSON + value for the string `"Unknown"`; this could be done by hand using + `View.OfBytes()`, but using `API.Deserialize` is even simpler: + */ + var UNKNOWN_JS :- expect ToUTF8Checked(@"""Unknown"""); + var UNKNOWN :- expect API.Deserialize(UNKNOWN_JS); + + /** + `UNKNOWN` is of type `Grammar.JSON`, which contains optional whitespace and + a `Grammar.Value` under the name `UNKNOWN.t`, which we can use in the + replacement: + */ + var without_null := deserialized.(t := ReplaceNull(deserialized.t, UNKNOWN.t)); + + /** + Then, if we reserialize, we see that all formatting (and, in fact, all of + the serialization work) has been reused: + */ + var expected_js :- expect ToUTF8Checked(@"{ + ""Cities"": [ + { + ""Name"": ""Boston"", + ""Founded"": 1630, + ""Population"": 689386, + ""Area (km2)"": 4600 + }, { + ""Name"": ""Rome"", + ""Founded"": -753, + ""Population"": 2.873e6, + ""Area (km2)"": 1285 + }, { + ""Name"": ""Paris"", + ""Founded"": ""Unknown"", + ""Population"": 2.161e6, + ""Area (km2)"": 2383.5 + } + ] + }"); + var actual_js :- expect API.Serialize(without_null); + expect actual_js == expected_js; + } + + /** All that remains is to write the recursive traversal: */ + function ReplaceNull(js: Value, replacement: Value): Value { + match js + /** Non-recursive cases are untouched: */ + case Bool(_) => js + case String(_) => js + case Number(_) => js + /** `Null` is replaced with the new `replacement` value: */ + case Null(_) => replacement + /** + … and objects and arrays are traversed recursively (only the data part of is + traversed: other fields record information about the formatting of braces, + square brackets, and whitespace, and can thus be reused without + modifications): + */ + case Object(obj) => + Object(obj.(data := MapSuffixedSequence(obj.data, (s: Suffixed) requires s in obj.data => + s.t.(v := ReplaceNull(s.t.v, replacement))))) + case Array(arr) => + Array(arr.(data := MapSuffixedSequence(arr.data, (s: Suffixed) requires s in arr.data => + ReplaceNull(s.t, replacement)))) + } + + /** + Note that well-formedness criteria on the low-level AST are enforced using + subset types, which is why we need a bit more work to iterate over the + sequences of key-value paris and of values in objects and arrays. + Specifically, we need to prove that mapping over these sequences doesn't + introduce dangling punctuation (`NoTrailingSuffix`). We package this + reasoning into a `MapSuffixedSequence` function: + */ + function MapSuffixedSequence(sq: SuffixedSequence, fn: Suffixed --> D) + : SuffixedSequence + requires forall suffixed | suffixed in sq :: fn.requires(suffixed) + { + // BUG(https://github.com/dafny-lang/dafny/issues/2184) + // BUG(https://github.com/dafny-lang/dafny/issues/2690) + var fn' := (sf: Suffixed) requires (ghost var in_sq := sf => sf in sq; in_sq(sf)) => sf.(t := fn(sf)); + var sq' := Seqs.Map(fn', sq); + + assert NoTrailingSuffix(sq') by { + forall idx | 0 <= idx < |sq'| ensures sq'[idx].suffix.Empty? <==> idx == |sq'| - 1 { + calc { + sq'[idx].suffix.Empty?; + fn'(sq[idx]).suffix.Empty?; + sq[idx].suffix.Empty?; + idx == |sq| - 1; + idx == |sq'| - 1; + } + } + } + + sq' + } +} \ No newline at end of file diff --git a/Source/DafnyStandardLibraries/examples/JSON/JSONTests.dfy b/Source/DafnyStandardLibraries/examples/JSON/JSONTests.dfy new file mode 100644 index 00000000000..b32e0a65451 --- /dev/null +++ b/Source/DafnyStandardLibraries/examples/JSON/JSONTests.dfy @@ -0,0 +1,137 @@ +abstract module AbstractWrapper { + import DafnyStdLibs.Strings + import opened DafnyStdLibs.BoundedInts + import opened DafnyStdLibs.Unicode.UnicodeStringsWithUnicodeChar + import opened DafnyStdLibs.JSON.Errors + + type JSON + + method Deserialize(bs: bytes) returns (js: DeserializationResult) + method SpecSerialize(js: JSON) returns (bs: SerializationResult) + method Serialize(js: JSON) returns (bs: SerializationResult) + method Check(bs: bytes, js: JSON, bs': bytes, sbs': bytes, js': JSON) + + method TestBytestring(bs: bytes, indent: string) { + var js :- expect Deserialize(bs); + // print indent, "=> ", js, "\n"; + var bs' :- expect Serialize(js); + print indent, "=> ", FromUTF8Checked(bs'), "\n"; + var sbs' :- expect SpecSerialize(js); + print indent, "=> ", FromUTF8Checked(sbs'), "\n"; + var js' :- expect Deserialize(bs'); + Check(bs, js, bs', sbs', js'); + } + + method TestString(str: string, indent: string) { + var bs :- expect ToUTF8Checked(str); + TestBytestring(bs, indent); + } + + method TestStrings(vectors: seq) { + for i := 0 to |vectors| { + var input := vectors[i]; + var idx := Strings.OfInt(i); + var indent := seq(|idx| + 1, _ => ' '); + print "[", idx, "]: ", input, "\n"; + TestString(input, indent); + print "\n"; + } + } +} + +module ZeroCopyWrapper refines AbstractWrapper { + import opened DafnyStdLibs.Wrappers + import DafnyStdLibs.JSON.Grammar + import DafnyStdLibs.JSON.ZeroCopy.API + import DafnyStdLibs.JSON.ConcreteSyntax.Spec + + type JSON = Grammar.JSON + + method Deserialize(bs: bytes) returns (js: DeserializationResult) { + js := API.Deserialize(bs); + } + + method Serialize(js: JSON) returns (bs: SerializationResult) { + // print "Count: ", wr.chain.Count(), "\n"; + bs := API.Serialize(js); + } + + method SpecSerialize(js: JSON) returns (bs: SerializationResult) { + bs := Success(Spec.JSON(js)); + } + + method Check(bs: bytes, js: JSON, bs': bytes, sbs': bytes, js': JSON) { + expect sbs' == bs' == bs; + expect js' == js; // This doesn't hold in general, since the views could be different + } +} + +module AbstractSyntaxWrapper refines AbstractWrapper { + import opened DafnyStdLibs.Wrappers + import DafnyStdLibs.JSON.Grammar + import DafnyStdLibs.JSON.API + import DafnyStdLibs.JSON.Values + import DafnyStdLibs.JSON.Spec + + type JSON = Values.JSON + + method Deserialize(bs: bytes) returns (js: DeserializationResult) { + js := API.Deserialize(bs); + } + + method Serialize(js: JSON) returns (bs: SerializationResult) { + bs := API.Serialize(js); + } + + method SpecSerialize(js: JSON) returns (bs: SerializationResult) { + bs := Spec.JSON(js); + } + + method Check(bs: bytes, js: JSON, bs': bytes, sbs': bytes, js': JSON) { + expect sbs' == bs'; // Serializing changes number representations, escapes, and spacing, so no == bs + expect js' == js; + } +} + +module MainTests { + import ZeroCopyWrapper + import AbstractSyntaxWrapper + import opened DafnyStdLibs.Collections.Seqs + + const VECTORS := [ + "true", + "false", + "null", + "\"\"", + "\"string\"", + "[\"A\"]", + "-123.456e-18", + "[]", + "[ ]", + "[1]", + "[1, 2]", + "{}", + "{ \"a\": 1 }", + "{ \"a\": \"b\" }", + "{ \"some\" : \"string\", \"and\": [ \"a number\", -123.456e-18 ] }", + + " true ", + " { } ", + "\"\\t\\r\\n\\f\"", + "\"∀ABC // \\u2200ABC\"", // ∀ + "\"🇫🇷 // \\u1f1eb\\u1f1EBABC\"", // 🇫🇷 + + "[true, false , null, { \"some\" : \"string\", \"and\": [ \"a number\", -123.456e-18 ] } ] ", + + /** + Stress test - this used to cause stack overflow errors because of non-tail-recursive functions. + We should have these kinds of tests direclty in the Unicode module too. + */ + "\"" + Seqs.Repeat('a', 100) + "\"" + ] + + method {:test} Main() { + ZeroCopyWrapper.TestStrings(VECTORS); + AbstractSyntaxWrapper.TestStrings(VECTORS); + } +} \ No newline at end of file diff --git a/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/BoundedInts.dfy b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/BoundedInts.dfy index 6eda9f9d23a..0147e6d2323 100644 --- a/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/BoundedInts.dfy +++ b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/BoundedInts.dfy @@ -43,4 +43,27 @@ module DafnyStdLibs.BoundedInts { newtype nat32 = x: int | 0 <= x < TWO_TO_THE_31 newtype nat64 = x: int | 0 <= x < TWO_TO_THE_63 newtype nat128 = x: int | 0 <= x < TWO_TO_THE_127 + + const UINT8_MAX: uint8 := 0xFF + const UINT16_MAX: uint16 := 0xFFFF + const UINT32_MAX: uint32 := 0xFFFF_FFFF + const UINT64_MAX: uint64 := 0xFFFF_FFFF_FFFF_FFFF + + const INT8_MIN: int8 := -0x80 + const INT8_MAX: int8 := 0x7F + const INT16_MIN: int16 := -0x8000 + const INT16_MAX: int16 := 0x7FFF + const INT32_MIN: int32 := -0x8000_0000 + const INT32_MAX: int32 := 0x7FFFFFFF + const INT64_MIN: int64 := -0x8000_0000_0000_0000 + const INT64_MAX: int64 := 0x7FFFFFFF_FFFFFFFF + + const NAT8_MAX: nat8 := 0x7F + const NAT16_MAX: nat16 := 0x7FFF + const NAT32_MAX: nat32 := 0x7FFFFFFF + const NAT64_MAX: nat64 := 0x7FFFFFFF_FFFFFFFF + + type byte = uint8 + type bytes = seq + newtype opt_byte = c: int | -1 <= c < TWO_TO_THE_8 } diff --git a/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/Collections/Seqs.dfy b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/Collections/Seqs.dfy index f3b4b854a10..a8fcfd83584 100644 --- a/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/Collections/Seqs.dfy +++ b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/Collections/Seqs.dfy @@ -73,12 +73,23 @@ module DafnyStdLibs.Collections.Seqs { { } - /* The concatenation of sequences is associative. */ + /* The concatenation of sequences is associative (three arguments). */ lemma LemmaConcatIsAssociative(xs: seq, ys: seq, zs: seq) - ensures xs + (ys + zs) == (xs + ys) + zs + ensures xs + (ys + zs) == xs + ys + zs == (xs + ys) + zs { } + /* The concatenation of sequences is associative (four arguments). */ + lemma LemmaConcatIsAssociative2(a: seq, b: seq, c: seq, d: seq) + ensures a + b + c + d == a + (b + c + d) + { + } + + /* The empty sequence is the right identity of the concatenation operation. */ + lemma EmptySequenceIsRightIdentity(l: seq) + ensures l == l + [] + {} + /********************************************************** * * Manipulating the Content of a Sequence @@ -390,6 +401,16 @@ module DafnyStdLibs.Collections.Seqs { { } + /* If a predicate is true for every member of a sequence as a collection, + it is true at every index of the sequence. + Useful for converting quantifiers between the two forms + to satisfy a precondition in the latter form. */ + lemma MembershipImpliesIndexing(p: T -> bool, xs: seq) + requires forall t | t in xs :: p(t) + ensures forall i | 0 <= i < |xs| :: p(xs[i]) + { + } + /********************************************************** * * Extrema in Sequences @@ -624,7 +645,7 @@ module DafnyStdLibs.Collections.Seqs { /* Applying a function to a sequence is distributive over concatenation. That is, concatenating two sequences and then applying Map is the same as applying Map to each sequence separately, and then concatenating the two resulting sequences. */ - lemma {:opaque} {:rlimit 2000} LemmaMapDistributesOverConcat(f: (T ~> R), xs: seq, ys: seq) + lemma {:opaque} {:rlimit 1000000} LemmaMapDistributesOverConcat(f: (T ~> R), xs: seq, ys: seq) requires forall i {:trigger xs[i]}:: 0 <= i < |xs| ==> f.requires(xs[i]) requires forall j {:trigger ys[j]}:: 0 <= j < |ys| ==> f.requires(ys[j]) ensures Map(f, xs + ys) == Map(f, xs) + Map(f, ys) diff --git a/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/API.dfy b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/API.dfy new file mode 100644 index 00000000000..9e77a70a258 --- /dev/null +++ b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/API.dfy @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright by the contributors to the Dafny Project + * SPDX-License-Identifier: MIT + *******************************************************************************/ + +/** + The high-level API of the JSON library. This version is built on top of the low-level (zero-copy) API. + This API is more convenient to use, but it is unverified and less efficient. It produces abstract datatype + value trees that represent strings using Dafny's built-in `string` type. + */ +module DafnyStdLibs.JSON.API { + import Values + import Serializer + import Deserializer + import ZeroCopy = ZeroCopy.API + import opened BoundedInts + import opened Errors + + /* Serialization (JSON values to utf-8 bytes) */ + opaque function Serialize(js: Values.JSON) : (bs: SerializationResult>) { + var js :- Serializer.JSON(js); + ZeroCopy.Serialize(js) + } + + method SerializeAlloc(js: Values.JSON) returns (bs: SerializationResult>) { + var js :- Serializer.JSON(js); + bs := ZeroCopy.SerializeAlloc(js); + } + + method SerializeInto(js: Values.JSON, bs: array) returns (len: SerializationResult) + modifies bs + { + var js :- Serializer.JSON(js); + len := ZeroCopy.SerializeInto(js, bs); + } + + /* Deserialization (utf-8 bytes to JSON values) */ + opaque function Deserialize(bs: seq) : (js: DeserializationResult) { + var js :- ZeroCopy.Deserialize(bs); + Deserializer.JSON(js) + } +} \ No newline at end of file diff --git a/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/ByteStrConversion.dfy b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/ByteStrConversion.dfy new file mode 100644 index 00000000000..8d5893b347d --- /dev/null +++ b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/ByteStrConversion.dfy @@ -0,0 +1,25 @@ +/******************************************************************************* + * Copyright by the contributors to the Dafny Project + * SPDX-License-Identifier: MIT + *******************************************************************************/ + +/** + Derives conversions to and from sequences of bytes. + */ +module DafnyStdLibs.JSON.ByteStrConversion refines Strings.ParametricConversion { + import opened BoundedInts + + type Char = byte + + const chars := [ + '0' as byte, '1' as byte, '2' as byte, '3' as byte, + '4' as byte, '5' as byte, '6' as byte, '7' as byte, + '8' as byte, '9' as byte + ] + + const charToDigit := map[ + '0' as byte := 0, '1' as byte := 1, '2' as byte := 2, '3' as byte := 3, + '4' as byte := 4, '5' as byte := 5, '6' as byte := 6, '7' as byte := 7, + '8' as byte := 8, '9' as byte := 9 + ] +} \ No newline at end of file diff --git a/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/ConcreteSyntax.Spec.dfy b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/ConcreteSyntax.Spec.dfy new file mode 100644 index 00000000000..d15ef321369 --- /dev/null +++ b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/ConcreteSyntax.Spec.dfy @@ -0,0 +1,130 @@ +/******************************************************************************* + * Copyright by the contributors to the Dafny Project + * SPDX-License-Identifier: MIT + *******************************************************************************/ + +/** + Defines a low-level specification of a JSON serializer. + */ +module DafnyStdLibs.JSON.ConcreteSyntax.Spec { + import Vs = Utils.Views.Core + import opened BoundedInts + import opened Grammar + + function View(v: Vs.View) : bytes { + v.Bytes() + } + + function Structural(self: Structural, fT: T -> bytes): bytes { + View(self.before) + fT(self.t) + View(self.after) + } + + function StructuralView(self: Structural): bytes { + Structural(self, View) + } + + function Maybe(self: Maybe, fT: T -> bytes): (bs: bytes) + ensures self.Empty? ==> bs == [] + ensures self.NonEmpty? ==> bs == fT(self.t) + { + if self.Empty? then [] else fT(self.t) + } + + function ConcatBytes(ts: seq, fT: T --> bytes) : (b: bytes) + requires forall d | d in ts :: fT.requires(d) + ensures |ts| == 1 ==> b == fT(ts[0]) + { + if |ts| == 0 then [] + else fT(ts[0]) + ConcatBytes(ts[1..], fT) + } + + function Bracketed(self: Bracketed, fDatum: Suffixed --> bytes): bytes + requires forall d | d < self :: fDatum.requires(d) + { + StructuralView(self.l) + + ConcatBytes(self.data, fDatum) + + StructuralView(self.r) + } + + function KeyValue(self: jKeyValue): bytes { + String(self.k) + StructuralView(self.colon) + Value(self.v) + } + + function Frac(self: jfrac): bytes { + View(self.period) + View(self.num) + } + + function Exp(self: jexp): bytes { + View(self.e) + View(self.sign) + View(self.num) + } + + function Number(self: jnumber): bytes { + View(self.minus) + View(self.num) + Maybe(self.frac, Frac) + Maybe(self.exp, Exp) + } + + function String(self: jstring): bytes { + View(self.lq) + View(self.contents) + View(self.rq) + } + + function CommaSuffix(c: Maybe>): bytes { + // BUG(https://github.com/dafny-lang/dafny/issues/2179) + Maybe>(c, StructuralView) + } + + function Member(self: jmember) : bytes { + KeyValue(self.t) + CommaSuffix(self.suffix) + } + + function Item(self: jitem) : bytes { + Value(self.t) + CommaSuffix(self.suffix) + } + + function Object(obj: jobject) : bytes { + Bracketed(obj, (d: jmember) requires d < obj => Member(d)) + } + + function Array(arr: jarray) : bytes { + Bracketed(arr, (d: jitem) requires d < arr => Item(d)) + } + + function Value(self: Value) : (b: bytes) + ensures self.String? ==> b == String(self.str) + ensures self.Number? ==> b == Number(self.num) + ensures self.Object? ==> b == Object(self.obj) + ensures self.Array? ==> b == Array(self.arr) + { + match self { + case Null(n) => View(n) + case Bool(b) => View(b) + case String(str) => String(str) + case Number(num) => Number(num) + case Object(obj) => Object(obj) + case Array(arr) => Array(arr) + } + } + + lemma UnfoldValueNumber(v: Value) + requires v.Number? + ensures Value(v) == Number(v.num) + { + assert Value(v) == match v { case Number(num) => Number(num) case _ => []}; + } + + lemma UnfoldValueObject(v: Value) + requires v.Object? + ensures Value(v) == Object(v.obj) + { + assert Value(v) == match v { case Object(obj) => Object(obj) case _ => []}; + } + + lemma UnfoldValueArray(v: Value) + requires v.Array? + ensures Value(v) == Array(v.arr) + { + assert Value(v) == match v { case Array(arr) => Array(arr) case _ => []}; + } + + function JSON(js: JSON) : bytes { + Structural(js, Value) + } +} \ No newline at end of file diff --git a/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/ConcreteSyntax.SpecProperties.dfy b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/ConcreteSyntax.SpecProperties.dfy new file mode 100644 index 00000000000..12c7d43b382 --- /dev/null +++ b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/ConcreteSyntax.SpecProperties.dfy @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright by the contributors to the Dafny Project + * SPDX-License-Identifier: MIT + *******************************************************************************/ + +/** + Defines a simple functional specification of a JSON serializer. + */ +module DafnyStdLibs.JSON.ConcreteSyntax.SpecProperties { + import Spec + import Vs = Utils.Views.Core + import opened BoundedInts + import opened Grammar + + ghost predicate Bracketed_Morphism_Requires(bracketed: Bracketed, pd0: Suffixed --> bytes, pd1: Suffixed --> bytes) { + && (forall d | d < bracketed :: pd0.requires(d)) + && (forall d | d < bracketed :: pd1.requires(d)) + && (forall d | d < bracketed :: pd0(d) == pd1(d)) + } + + lemma Bracketed_Morphism(bracketed: Bracketed, pd0: Suffixed --> bytes, pd1: Suffixed --> bytes) + requires Bracketed_Morphism_Requires(bracketed, pd0, pd1) + ensures Spec.Bracketed(bracketed, pd0) == Spec.Bracketed(bracketed, pd1) + { + calc { + Spec.Bracketed(bracketed, pd0); + { ConcatBytes_Morphism(bracketed.data, pd0, pd1); } + Spec.Bracketed(bracketed, pd1); + } + } + + lemma {:induction ts} ConcatBytes_Morphism(ts: seq, pt0: T --> bytes, pt1: T --> bytes) + requires forall d | d in ts :: pt0.requires(d) + requires forall d | d in ts :: pt1.requires(d) + requires forall d | d in ts :: pt0(d) == pt1(d) + ensures Spec.ConcatBytes(ts, pt0) == Spec.ConcatBytes(ts, pt1) + {} + + lemma {:induction ts0} {:rlimit 10000} ConcatBytes_Linear(ts0: seq, ts1: seq, pt: T --> bytes) + requires forall d | d in ts0 :: pt.requires(d) + requires forall d | d in ts1 :: pt.requires(d) + ensures Spec.ConcatBytes(ts0 + ts1, pt) == Spec.ConcatBytes(ts0, pt) + Spec.ConcatBytes(ts1, pt) + { + if |ts0| == 0 { + assert [] + ts1 == ts1; + } else { + assert ts0 + ts1 == [ts0[0]] + (ts0[1..] + ts1); + } + } +} \ No newline at end of file diff --git a/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Deserializer.dfy b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Deserializer.dfy new file mode 100644 index 00000000000..5320d8f38eb --- /dev/null +++ b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Deserializer.dfy @@ -0,0 +1,163 @@ +/******************************************************************************* + * Copyright by the contributors to the Dafny Project + * SPDX-License-Identifier: MIT + *******************************************************************************/ + +/** + Implements high-level deserialization (utf-8 bytes to JSON values). + */ +module DafnyStdLibs.JSON.Deserializer { + import Values + import Spec + import ByteStrConversion + import opened Collections.Seqs + import opened Wrappers + import opened BoundedInts + import opened Arithmetic.Logarithm + import opened Arithmetic.Power + import opened Strings + import opened Unicode.UnicodeStringsWithUnicodeChar + import opened Errors + import opened DynamicArray + import opened Grammar + import opened Utils.Views.Core + + function Bool(js: Grammar.jbool): bool { + assert js.Bytes() in {Grammar.TRUE, Grammar.FALSE}; + js.At(0) == 't' as byte + } + + function UnsupportedEscape16(code: seq): DeserializationError { + UnsupportedEscape(FromUTF16Checked(code).GetOr("Couldn't decode UTF-16")) + } + + module Uint16StrConversion refines Strings.ParametricConversion { + import opened BoundedInts + + type Char = uint16 + + const chars := [ + '0' as uint16, '1' as uint16, '2' as uint16, '3' as uint16, '4' as uint16, + '5' as uint16, '6' as uint16, '7' as uint16, '8' as uint16, '9' as uint16, + 'a' as uint16, 'b' as uint16, 'c' as uint16, 'd' as uint16, 'e' as uint16, 'f' as uint16, + 'A' as uint16, 'B' as uint16, 'C' as uint16, 'D' as uint16, 'E' as uint16, 'F' as uint16 + ] + + const charToDigit := + map[ + '0' as uint16 := 0, '1' as uint16 := 1, '2' as uint16 := 2, '3' as uint16 := 3, '4' as uint16 := 4, + '5' as uint16 := 5, '6' as uint16 := 6, '7' as uint16 := 7, '8' as uint16 := 8, '9' as uint16 := 9, + 'a' as uint16 := 0xA, 'b' as uint16 := 0xB, 'c' as uint16 := 0xC, 'd' as uint16 := 0xD, 'e' as uint16 := 0xE, 'f' as uint16 := 0xF, + 'A' as uint16 := 0xA, 'B' as uint16 := 0xB, 'C' as uint16 := 0xC, 'D' as uint16 := 0xD, 'E' as uint16 := 0xE, 'F' as uint16 := 0xF + ] + } + + const HEX_TABLE_16 := Uint16StrConversion.charToDigit + + function ToNat16(str: Uint16StrConversion.String): uint16 + requires |str| <= 4 + requires forall c | c in str :: c in HEX_TABLE_16 + { + assume {:axiom} false; // BUG Verification inconclusive + Uint16StrConversion.ToNatBound(str); + var hd := Uint16StrConversion.ToNat(str); + assert hd < 0x1_0000 by { reveal Pow(); } + hd as uint16 + } + + function {:tailrecursion} {:vcs_split_on_every_assert} Unescape(str: seq, start: nat := 0, prefix: seq := []): DeserializationResult> + decreases |str| - start + { // Assumes UTF-16 strings + if start >= |str| then Success(prefix) + else if str[start] == '\\' as uint16 then + if |str| == start + 1 then + Failure(EscapeAtEOS) + else + var c := str[start + 1]; + if c == 'u' as uint16 then + if |str| <= start + 6 then + Failure(EscapeAtEOS) + else + var code := str[start + 2..start + 6]; + if exists c | c in code :: c !in HEX_TABLE_16 then + Failure(UnsupportedEscape16(code)) + else + var hd := ToNat16(code); + Unescape(str, start + 6, prefix + [hd]) + else + var unescaped: uint16 := match c + case 0x22 => 0x22 as uint16 // \" => quotation mark + case 0x5C => 0x5C as uint16 // \\ => reverse solidus + case 0x62 => 0x08 as uint16 // \b => backspace + case 0x66 => 0x0C as uint16 // \f => form feed + case 0x6E => 0x0A as uint16 // \n => line feed + case 0x72 => 0x0D as uint16 // \r => carriage return + case 0x74 => 0x09 as uint16 // \t => tab + case _ => 0 as uint16; + if unescaped as int == 0 then + Failure(UnsupportedEscape16(str[start..start+2])) + else + Unescape(str, start + 2, prefix + [unescaped]) + else + Unescape(str, start + 1, prefix + [str[start]]) + } + + function String(js: Grammar.jstring): DeserializationResult { + var asUtf32 :- FromUTF8Checked(js.contents.Bytes()).ToResult(DeserializationError.InvalidUnicode); + var asUint16 :- ToUTF16Checked(asUtf32).ToResult(DeserializationError.InvalidUnicode); + var unescaped :- Unescape(asUint16); + FromUTF16Checked(unescaped).ToResult(DeserializationError.InvalidUnicode) + } + + const DIGITS := ByteStrConversion.charToDigit + + const MINUS := '-' as uint8 + + function ToInt(sign: jsign, n: jnum): DeserializationResult { + var n: int := ByteStrConversion.ToNat(n.Bytes()); + Success(if sign.Char?('-') then -n else n) + } + + function Number(js: Grammar.jnumber): DeserializationResult { + var JNumber(minus, num, frac, exp) := js; + var n :- + ToInt(minus, num); + var e10 :- match exp + case Empty => Success(0) + case NonEmpty(JExp(_, sign, num)) => ToInt(sign, num); + match frac + case Empty => Success(Values.Decimal(n, e10)) + case NonEmpty(JFrac(_, num)) => + var pow10 := num.Length() as int; + var frac :- ToInt(minus, num); + Success(Values.Decimal(n * Pow(10, pow10) + frac, e10 - pow10)) + } + + function KeyValue(js: Grammar.jKeyValue): DeserializationResult<(string, Values.JSON)> { + var k :- String(js.k); + var v :- Value(js.v); + Success((k, v)) + } + + function Object(js: Grammar.jobject): DeserializationResult> { + Seqs.MapWithResult(d requires d in js.data => KeyValue(d.t), js.data) + } + + function Array(js: Grammar.jarray): DeserializationResult> { + Seqs.MapWithResult(d requires d in js.data => Value(d.t), js.data) + } + + function Value(js: Grammar.Value): DeserializationResult { + match js + case Null(_) => Success(Values.Null()) + case Bool(b) => Success(Values.Bool(Bool(b))) + case String(str) => var s :- String(str); Success(Values.String(s)) + case Number(dec) => var n :- Number(dec); Success(Values.Number(n)) + case Object(obj) => var o :- Object(obj); Success(Values.Object(o)) + case Array(arr) => var a :- Array(arr); Success(Values.Array(a)) + } + + function JSON(js: Grammar.JSON): DeserializationResult { + Value(js.t) + } +} \ No newline at end of file diff --git a/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Errors.dfy b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Errors.dfy new file mode 100644 index 00000000000..51528b2e5f9 --- /dev/null +++ b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Errors.dfy @@ -0,0 +1,63 @@ +/******************************************************************************* + * Copyright by the contributors to the Dafny Project + * SPDX-License-Identifier: MIT + *******************************************************************************/ + +/** + Defines different types of errors that can occur during serialization and deserialization. + */ +module DafnyStdLibs.JSON.Errors { + import Wrappers + import Strings + import opened BoundedInts + + datatype DeserializationError = + | UnterminatedSequence + | UnsupportedEscape(str: string) + | EscapeAtEOS + | EmptyNumber + | ExpectingEOF + | IntOverflow + | ReachedEOF + | ExpectingByte(expected: byte, b: opt_byte) + | ExpectingAnyByte(expected_sq: seq, b: opt_byte) + | InvalidUnicode + { + function ToString() : string { + match this + case UnterminatedSequence => "Unterminated sequence" + case UnsupportedEscape(str) => "Unsupported escape sequence: " + str + case EscapeAtEOS => "Escape character at end of string" + case EmptyNumber => "Number must contain at least one digit" + case ExpectingEOF => "Expecting EOF" + case IntOverflow => "Input length does not fit in a 32-bit counter" + case ReachedEOF => "Reached EOF" + case ExpectingByte(b0, b) => + var c := if b > 0 then "'" + [b as char] + "'" else "EOF"; + "Expecting '" + [b0 as char] + "', read " + c + case ExpectingAnyByte(bs0, b) => + var c := if b > 0 then "'" + [b as char] + "'" else "EOF"; + var c0s := seq(|bs0|, idx requires 0 <= idx < |bs0| => bs0[idx] as char); + "Expecting one of '" + c0s + "', read " + c + case InvalidUnicode => "Invalid Unicode sequence" + } + } + + datatype SerializationError = + | OutOfMemory + | IntTooLarge(i: int) + | StringTooLong(s: string) + | InvalidUnicode + { + function ToString() : string { + match this + case OutOfMemory => "Out of memory" + case IntTooLarge(i: int) => "Integer too large: " + Strings.OfInt(i) + case StringTooLong(s: string) => "String too long: " + s + case InvalidUnicode => "Invalid Unicode sequence" + } + } + + type SerializationResult<+T> = Wrappers.Result + type DeserializationResult<+T> = Wrappers.Result +} \ No newline at end of file diff --git a/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Grammar.dfy b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Grammar.dfy new file mode 100644 index 00000000000..d54354a581d --- /dev/null +++ b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Grammar.dfy @@ -0,0 +1,99 @@ +/******************************************************************************* + * Copyright by the contributors to the Dafny Project + * SPDX-License-Identifier: MIT + *******************************************************************************/ + +/** + Defines concrete syntax trees that capture details of punctuation and blanks and represent. + */ +module DafnyStdLibs.JSON.Grammar { + import opened BoundedInts + import opened Utils.Views.Core + + const EMPTY := View.OfBytes([]) + const DOUBLEQUOTE := View.OfBytes(['\"' as byte]) + const PERIOD := View.OfBytes(['.' as byte]) + const E := View.OfBytes(['e' as byte]) + const COLON := View.OfBytes([':' as byte]) + const COMMA := View.OfBytes([',' as byte]) + const LBRACE := View.OfBytes(['{' as byte]) + const RBRACE := View.OfBytes(['}' as byte]) + const LBRACKET := View.OfBytes(['[' as byte]) + const RBRACKET := View.OfBytes([']' as byte]) + const MINUS := View.OfBytes(['-' as byte]) + + type jchar = v: View | v.Length() == 1 witness View.OfBytes(['b' as byte]) + type jquote = v: View | v.Char?('\"') witness DOUBLEQUOTE + type jperiod = v: View | v.Char?('.') witness PERIOD + type je = v: View | v.Char?('e') || v.Char?('E') witness E + type jcolon = v: View | v.Char?(':') witness COLON + type jcomma = v: View | v.Char?(',') witness COMMA + type jlbrace = v: View | v.Char?('{') witness LBRACE + type jrbrace = v: View | v.Char?('}') witness RBRACE + type jlbracket = v: View | v.Char?('[') witness LBRACKET + type jrbracket = v: View | v.Char?(']') witness RBRACKET + type jminus = v: View | v.Char?('-') || v.Empty? witness MINUS + type jsign = v: View | v.Char?('-') || v.Char?('+') || v.Empty? witness EMPTY + + predicate Blank?(b: byte) { b == 0x20 || b == 0x09 || b == 0x0A || b == 0x0D } + ghost predicate Blanks?(v: View) { forall b | b in v.Bytes() :: Blank?(b) } + type jblanks = v: View | Blanks?(v) witness View.OfBytes([]) + + datatype Structural<+T> = + Structural(before: jblanks, t: T, after: jblanks) + + datatype Maybe<+T> = Empty() | NonEmpty(t: T) + + datatype Suffixed<+T, +S> = + Suffixed(t: T, suffix: Maybe>) + + type SuffixedSequence<+D, +S> = s: seq> | NoTrailingSuffix(s) + ghost predicate NoTrailingSuffix(s: seq>) { + forall idx | 0 <= idx < |s| :: s[idx].suffix.Empty? <==> idx == |s| - 1 + } + + datatype Bracketed<+L, +D, +S, +R> = + Bracketed(l: Structural, data: SuffixedSequence, r: Structural) + + const NULL: bytes := ['n' as byte, 'u' as byte, 'l' as byte, 'l' as byte] + const TRUE: bytes := ['t' as byte, 'r' as byte, 'u' as byte, 'e' as byte] + const FALSE: bytes := ['f' as byte, 'a' as byte, 'l' as byte, 's' as byte, 'e' as byte] + + ghost predicate Null?(v: View) { v.Bytes() == NULL } + ghost predicate Bool?(v: View) { v.Bytes() in {TRUE, FALSE} } + predicate Digit?(b: byte) { '0' as byte <= b <= '9' as byte } + ghost predicate Digits?(v: View) { forall b | b in v.Bytes() :: Digit?(b) } + ghost predicate Num?(v: View) { Digits?(v) && !v.Empty? } + ghost predicate Int?(v: View) { v.Char?('0') || (Num?(v) && v.At(0) != '0' as byte) } + + type jnull = v: View | Null?(v) witness View.OfBytes(NULL) + type jbool = v: View | Bool?(v) witness View.OfBytes(TRUE) + type jdigits = v: View | Digits?(v) witness View.OfBytes([]) + type jnum = v: View | Num?(v) witness View.OfBytes(['0' as byte]) + type jint = v: View | Int?(v) witness View.OfBytes(['0' as byte]) + type jstr = v: View | true witness View.OfBytes([]) // TODO: Enforce quoting and escaping + datatype jstring = JString(lq: jquote, contents: jstr, rq: jquote) + datatype jKeyValue = KeyValue(k: jstring, colon: Structural, v: Value) + + // TODO enforce no leading space before closing bracket to disambiguate WS { WS WS } WS + type jobject = Bracketed + type jarray = Bracketed + type jmembers = SuffixedSequence + type jmember = Suffixed + type jitems = SuffixedSequence + type jitem = Suffixed + + datatype jfrac = JFrac(period: jperiod, num: jnum) + datatype jexp = JExp(e: je, sign: jsign, num: jnum) + datatype jnumber = JNumber(minus: jminus, num: jnum, frac: Maybe, exp: Maybe) + + datatype Value = + | Null(n: jnull) + | Bool(b: jbool) + | String(str: jstring) + | Number(num: jnumber) + | Object(obj: jobject) + | Array(arr: jarray) + + type JSON = Structural +} \ No newline at end of file diff --git a/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/JSON.md b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/JSON.md new file mode 100644 index 00000000000..5eda93d4d64 --- /dev/null +++ b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/JSON.md @@ -0,0 +1,111 @@ +## The `JSON` module + +JSON serialization and deserialization in Dafny, as described in [RFC 8259](https://datatracker.ietf.org/doc/html/rfc8259). + +This library provides two APIs: + +- A low-level (zero-copy) API that is efficient, verified (see [What is verified?](#what-is-verified) below for details) and allows incremental changes (re-serialization is much faster for unchanged objects), but is more cumbersome to use. This API operates on concrete syntax trees that capture details of punctuation and blanks and represent strings using unescaped, undecoded utf-8 byte sequences. + +- A high-level API built on top of the previous one. This API is more convenient to use, but it is unverified and less efficient. It produces abstract datatype value trees that represent strings using Dafny's built-in `string` type. + +Both APIs provides functions for serialization (JSON values to utf-8 bytes) and deserialization (utf-8 bytes to JSON values). +See the Unicode module if you need to read or produce JSON text in other encodings. + +## Library usage + +The tutorial in [`JSONExamples.dfy`](../../../examples/JSON/JSONExamples.dfy) shows how to import the library, call the high-level API, and use the low-level API to make localized modifications to a partial parse of a JSON AST. The main entry points are `API.Serialize` (to go from a JSON value to utf-8 bytes), and `API.Deserialize` (for the reverse operation): + + +```dafny +import API = DafnyStdLibs.JSON.API +import opened DafnyStdLibs.Unicode.UnicodeStringsWithUnicodeChar +import opened DafnyStdLibs.JSON.Values +import opened DafnyStdLibs.Wrappers + +method Test(){ + var CITY_JS :- expect ToUTF8Checked(@"{""Cities"": [{ + ""Name"": ""Boston"", + ""Founded"": 1630, + ""Population"": 689386, + ""Area (km2)"": 4584.2}]}"); + + var CITY_VALUE := Object([("Cities", Array([ + Object([ + ("Name", String("Boston")), + ("Founded", Number(Int(1630))), + ("Population", Number(Int(689386))), + ("Area (km2)", Number(Decimal(45842, -1)))])]))]); + + expect API.Deserialize(CITY_JS) == Success(CITY_VALUE); + + var EXPECTED :- expect ToUTF8Checked( + @"{""Cities"":[{""Name"":""Boston"",""Founded"":1630,""Population"":689386,""Area (km2)"":45842e-1}]}" + ); + + expect API.Serialize(CITY_VALUE) == Success(EXPECTED); +} +``` + +## What is verified? + +The low-level (zero-copy) serializer is proven sound and complete against a simple functional specification found in [`ConcreteSyntax.Spec.dfy`](ConcreteSyntax.Spec.dfy). The high-level deserializer is proven sound, but not complete, against that same specification: if a value is deserialized successfully, then re-serializing recovers the original bytestring. + +### Useful submodules + +Most of the contents of the `Utils/` directory are not specific to JSON. They are not sufficiently documented to be moved to the main library yet, but they provide useful functionality: + +- [`Views.dfy`](Utils/Views.dfy) and [`Views.Writers.dfy`](Utils/Views.Writers.dfy) implement slices over byte strings whose bounds are representable as `int32` native integers. Adjacent slices can be merged in time `O(1)` (but see [“Caveats”](#caveats) below). To be part of the main library, it would have to be generalized to slices over any sequences and to indices represented using arbitrary integers (though Dafny does not currently have a way to have modularity over int width). + +- [`Cursors.dfy`](Utils/Cursors.dfy) implements slices augmented with an inner pointer tracking a position. Functions are provided to skip over a specific byte or bytes, over bytes matching a given predicate, or over bytes recognized by a given lexer, and ghost functions are provided to express the fact that a cursor was obtained by trimming a prefix of another cursor. Cursors are used in the implementation of the parser: after skipping over a given construct, a cursor can be split into a view (capturing the part of the string matching the construct) and a new cursor starting at the previous position, with its pointer reset to the beginning of the string. To become part of the main library, cursors would need to be generalized in the same way as views above. + +- [`Lexers.dfy`](Utils/Lexers.dfy) contains a state machine that recognizes quoted strings and skips over backslash-escaped quotes. State-machine-based lexers are used in `Cursors.dfy` to skip over complex constructs (like quoted strings), though in practice the low-level parser uses a custom function-by-method to speed up this specific case. + +- [`Parsers.dfy`](Utils/Parsers.dfy) has definitions of well-formedness for parsers (stating that they must consume part of their input). This file would have to be significantly expanded to create a composable library of parsing combinators to be useful as part of the main library. + +## Caveats + +- Not all parts of this library are verified. In particular, the high-level API is not verified (the most complex part of it is the string escaping code). + +- The optimization used to merge contiguous slices can have poor performance in corner cases. Specifically, the function `Views.Core.Adjacent`, which checks whether two slices can be concatenated efficiently, uses Dafny's value equality, not reference equality, to check whether both slices point into the same string. The value-equality check is `O(1)` when the strings are physically equal, but may take linear time otherwise. + + The worst-case behavior happens when serializing a low-level JSON object in which the `View`s all point to (physically) different strings with equal contents. This cannot happen when modifying the result of a previous parse (in that case, all views will share the same underlying storage, and the comparison will be fast). + + This issue could be fixed by using reference equality, but doing so requires defining an external function to expose (a restricted form of) reference equality on values (exposing reference equality in general on values is not sound). A good description of the relevant technique can be found in chapter 9 of “Verasco: a Formally Verified C Static Analyzer” by Jacques-Henri Jourdan. + +### Implementation notes + +- The division into a low-level and a high-level API achieves two desirable outcomes: + + - Programs that modify a small part of a JSON object can be implemented much more efficiently that using a traditional JSON AST ↔ bytes API, since unmodified parts of the decoded JSON object can be reserialized very quickly. + + - Specs and proofs for the low-level API are simple because the low-level API captures all encoding details (including the amount of whitespace used), which makes serialization and deserialization uniquely determined. As a result, serialization is really the (left) inverse of deserializations. + +- Most of the low-level API uses bounded integers (`int32`), so the library cannot deserialize more than 4GB of JSON text. + +- To support in-place updates, the low-level API supports serializing into an existing buffer. Instead of repeatedly checking for overflows, the implementation uses saturating addition and checks for overflow only once, after writing the whole object. This optimization was inspired by Go's error-handling style, described in [Errors are values](https://go.dev/blog/errors-are-values). + +- Dafny does not support floating point numbers (and the JSON spec does not mandate a specific precision), so the library uses a pair `(n, e10)` instead representing the number `n * 10^e10`. + +- Workarounds for known Dafny bugs are indicated by the keyword [`BUG`](https://github.com/dafny-lang/libraries/search?q=BUG) in the sources. + +## TODOs and contribution opportunities + +### Verification + +The high-level API is unverified. Unlike the low-level API, general JSON serialization and deserialization are not inverses of each other, because deserializing an object and re-serializing it may add or remove whitespace or change the way strings or numbers are represented (e.g. the string `"a"` may be serialized as `"\u0061"` and the number 1 as 1.0e0, 10e-1, etc.). As a result, an executable serializer is not a sufficient specification of the format. Techniques to write specifications and verify serializers and deserializers in such cases are described in e.g. [Narcissus: correct-by-construction derivation of decoders and encoders from binary formats](https://dl.acm.org/doi/abs/10.1145/3341686). + +Note that it would be possible (and a great start) to show that the deserializer supports everything that the serializer may produce. This would not require a generalized specification (one that allows variation in serialization choices) of the format. + +Beyond this large task, some small lemmas that should hold but were not needed by this codebase are commented out and marked as `// TODO`. + +### Performance problems in the high-level API + +The high-level API does string encoding and escaping as two separate passes that both construct sequences. Adding `by method` blocks and fusing these two passes would provide significant gains. + +### Performance opportunities in the low-level API + +The low-level API is written in the pure subset of Dafny. As a result, it creates lots of short-lived records (especially cursors, defined in [`Cursors.dfy`](Utils/Cursors.dfy)). A linear version of Dafny would be able to mutate these structures in place. A traditional reuse analysis (see e.g. Schulte & Grieskamp, “Generating Efficient Portable Code for a Strict Applicative Language”, or any of the OPAL papers) would achieve the same result. Large gains could already be achieved simply by improving the compilation of records in the C# backend (e.g. using structs for small records and removing unnecessary interfaces). + +The optimization to merge contiguous string could be made to use physical equality. This would provide little gain in the typical case, but would eliminate the worst-case described in [“Caveats”](#caveats) above. + +The low-level serializer does not re-serialize unchanged parts of an AST, but it still re-writes the whole object. This could be eliminated when changes to the object do not change its overall length. \ No newline at end of file diff --git a/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Serializer.dfy b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Serializer.dfy new file mode 100644 index 00000000000..acfcfa279ab --- /dev/null +++ b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Serializer.dfy @@ -0,0 +1,136 @@ +/******************************************************************************* + * Copyright by the contributors to the Dafny Project + * SPDX-License-Identifier: MIT + *******************************************************************************/ + +/** + Implements high-level serialization (JSON values to utf-8 bytes). + */ +module DafnyStdLibs.JSON.Serializer { + import Collections.Seqs + import Math + import Values + import Spec + import opened Wrappers + import opened BoundedInts + import opened Strings + import opened Errors + import opened DynamicArray + import opened Grammar + import opened Utils.Views.Core + import ByteStrConversion + + type Result<+T> = SerializationResult + type bytes = seq + type bytes32 = bs: bytes | |bs| < TWO_TO_THE_32 + type string32 = s: string | |s| < TWO_TO_THE_32 + + function Bool(b: bool): jbool { + View.OfBytes(if b then TRUE else FALSE) + } + + function CheckLength(s: seq, err: SerializationError): Outcome { + Outcome.Need(|s| < TWO_TO_THE_32, err) + } + + function String(str: string): Result { + var bs :- Spec.EscapeToUTF8(str); + var o := CheckLength(bs, StringTooLong(str)); + if o.Pass? then + Success(Grammar.JString(Grammar.DOUBLEQUOTE, View.OfBytes(bs), Grammar.DOUBLEQUOTE)) + else + Failure(o.error) + } + + function Sign(n: int): jminus { + View.OfBytes(if n < 0 then ['-' as byte] else []) + } + + const DIGITS := ByteStrConversion.chars + const MINUS := '-' as byte + + function Int'(n: int): (str: bytes) + ensures forall c | c in str :: c in DIGITS || c == MINUS + { + ByteStrConversion.OfInt(n, MINUS) + } + + function Int(n: int): Result { + var bs := Int'(n); + var o := CheckLength(bs, IntTooLarge(n)); + if o.Pass? then + Success(View.OfBytes(bs)) + else + Failure(o.error) + } + + function {:vcs_split_on_every_assert} {:rlimit 1000} Number(dec: Values.Decimal): Result { + var minus: jminus := Sign(dec.n); + var num: jnum :- Int(Math.Abs(dec.n)); + var frac: Maybe := Empty(); + var exp: Maybe :- + if dec.e10 == 0 then + Success(Empty()) + else + var e: je := View.OfBytes(['e' as byte]); + var sign: jsign := Sign(dec.e10); + var num: jnum :- Int(Math.Abs(dec.e10)); + Success(NonEmpty(JExp(e, sign, num))); + Success(JNumber(minus, num, Empty, exp)) + } + + function MkStructural(v: T): Structural { + Structural(EMPTY, v, EMPTY) + } + + const COLON: Structural := + MkStructural(Grammar.COLON) + + function KeyValue(kv: (string, Values.JSON)): Result { + var k :- String(kv.0); + var v :- Value(kv.1); + Success(Grammar.KeyValue(k, COLON, v)) + } + + function MkSuffixedSequence(ds: seq, suffix: Structural, start: nat := 0) + : SuffixedSequence + decreases |ds| - start + { + if start >= |ds| then [] + else if start == |ds| - 1 then [Suffixed(ds[start], Empty)] + else [Suffixed(ds[start], NonEmpty(suffix))] + MkSuffixedSequence(ds, suffix, start + 1) + } + + const COMMA: Structural := + MkStructural(Grammar.COMMA) + + function Object(obj: seq<(string, Values.JSON)>): Result { + var items :- Seqs.MapWithResult(v requires v in obj => KeyValue(v), obj); + Success(Bracketed(MkStructural(LBRACE), + MkSuffixedSequence(items, COMMA), + MkStructural(RBRACE))) + } + + + function Array(arr: seq): Result { + var items :- Seqs.MapWithResult(v requires v in arr => Value(v), arr); + Success(Bracketed(MkStructural(LBRACKET), + MkSuffixedSequence(items, COMMA), + MkStructural(RBRACKET))) + } + + function Value(js: Values.JSON): Result { + match js + case Null => Success(Grammar.Null(View.OfBytes(NULL))) + case Bool(b) => Success(Grammar.Bool(Bool(b))) + case String(str) => var s :- String(str); Success(Grammar.String(s)) + case Number(dec) => var n :- Number(dec); Success(Grammar.Number(n)) + case Object(obj) => var o :- Object(obj); Success(Grammar.Object(o)) + case Array(arr) => var a :- Array(arr); Success(Grammar.Array(a)) + } + + function JSON(js: Values.JSON): Result { + var val :- Value(js); + Success(MkStructural(val)) + } +} \ No newline at end of file diff --git a/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Spec.dfy b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Spec.dfy new file mode 100644 index 00000000000..c8216cfc1fa --- /dev/null +++ b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Spec.dfy @@ -0,0 +1,132 @@ +/******************************************************************************* + * Copyright by the contributors to the Dafny Project + * SPDX-License-Identifier: MIT + *******************************************************************************/ + +/** + Defines a high-level specification of a JSON serializer. + */ +module DafnyStdLibs.JSON.Spec { + import Collections.Seqs + import opened BoundedInts + import opened Strings + import opened Values + import opened Wrappers + import opened Errors + import opened Unicode.UnicodeStringsWithUnicodeChar + import opened Arithmetic.Logarithm + + type Result<+T> = SerializationResult + + function EscapeUnicode(c: uint16): seq { + var sStr := Strings.HexConversion.OfNat(c as nat); + Seqs.MembershipImpliesIndexing(c => 0 <= c as int < 128, sStr); + var s := ASCIIToUTF16(sStr); + assert |s| <= 4 by { + assert c as nat <= 0xFFFF; + assert Log(16, c as nat) <= Log(16, 0xFFFF) by { + LemmaLogIsOrdered(16, c as nat, 0xFFFF); + } + assert Log(16, 0xFFFF) == 3 by { reveal Log(); } + } + s + seq(4 - |s|, _ => ' ' as uint16) + } + + function Escape(str: seq, start: nat := 0): seq + decreases |str| - start + { + if start >= |str| then [] + else + (match str[start] + case 0x22 => ASCIIToUTF16("\\\"") // quotation mark + case 0x5C => ASCIIToUTF16("\\\\") // reverse solidus + case 0x08 => ASCIIToUTF16("\\b") // backspace + case 0x0C => ASCIIToUTF16("\\f") // form feed + case 0x0A => ASCIIToUTF16("\\n") // line feed + case 0x0D => ASCIIToUTF16("\\r") // carriage return + case 0x09 => ASCIIToUTF16("\\t") // tab + case c => + if c < 0x001F then ASCIIToUTF16("\\u") + EscapeUnicode(c) + else [str[start]]) + + Escape(str, start + 1) + } + + function EscapeToUTF8(str: string, start: nat := 0): Result { + var utf16 :- ToUTF16Checked(str).ToResult(SerializationError.InvalidUnicode); + var escaped := Escape(utf16); + var utf32 :- FromUTF16Checked(escaped).ToResult(SerializationError.InvalidUnicode); + ToUTF8Checked(utf32).ToResult(SerializationError.InvalidUnicode) + } + + // Can fail due to invalid UTF-16 sequences in a string when --unicode-char is off + function String(str: string): Result { + var inBytes :- EscapeToUTF8(str); + Success(ASCIIToUTF8("\"") + inBytes + ASCIIToUTF8("\"")) + } + + lemma OfIntOnlyASCII(n: int) + ensures + && var s := Strings.OfInt(n); + && forall i | 0 <= i < |s| :: 0 <= s[i] as int < 128 + { + var s := Strings.OfInt(n); + forall i | 0 <= i < |s| ensures 0 <= s[i] as int < 128 { + if i == 0 { + } else { + var isHexDigit := c => c in Strings.HexConversion.HEX_DIGITS; + assert Strings.HexConversion.OfNumberStr(s, '-'); + assert isHexDigit(s[i]); + } + } + } + + function IntToBytes(n: int): bytes { + var s := Strings.OfInt(n); + OfIntOnlyASCII(n); + ASCIIToUTF8(s) + } + + function Number(dec: Decimal): Result { + Success(IntToBytes(dec.n) + + (if dec.e10 == 0 then [] + else ASCIIToUTF8("e") + IntToBytes(dec.e10))) + } + + function KeyValue(kv: (string, JSON)): Result { + var key :- String(kv.0); + var value :- JSON(kv.1); + Success(key + ASCIIToUTF8(":") + value) + } + + function Join(sep: bytes, items: seq>): Result { + if |items| == 0 then + Success([]) + else + var first :- items[0]; + if |items| == 1 then + Success(first) + else + var rest :- Join(sep, items[1..]); + Success(first + sep + rest) + } + + function Object(obj: seq<(string, JSON)>): Result { + var middle :- Join(ASCIIToUTF8(","), seq(|obj|, i requires 0 <= i < |obj| => KeyValue(obj[i]))); + Success(ASCIIToUTF8("{") + middle + ASCIIToUTF8("}")) + } + + function Array(arr: seq): Result { + var middle :- Join(ASCIIToUTF8(","), seq(|arr|, i requires 0 <= i < |arr| => JSON(arr[i]))); + Success(ASCIIToUTF8("[") + middle + ASCIIToUTF8("]")) + } + + function JSON(js: JSON): Result { + match js + case Null => Success(ASCIIToUTF8("null")) + case Bool(b) => Success(if b then ASCIIToUTF8("true") else ASCIIToUTF8("false")) + case String(str) => String(str) + case Number(dec) => Number(dec) + case Object(obj) => Object(obj) + case Array(arr) => Array(arr) + } +} \ No newline at end of file diff --git a/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Utils/Cursors.dfy b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Utils/Cursors.dfy new file mode 100644 index 00000000000..b221a6c47d4 --- /dev/null +++ b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Utils/Cursors.dfy @@ -0,0 +1,340 @@ +/******************************************************************************* + * Copyright by the contributors to the Dafny Project + * SPDX-License-Identifier: MIT + *******************************************************************************/ + +/** + Implements slices augmented with an inner pointer tracking a position. + */ +module DafnyStdLibs.JSON.Utils.Cursors { + import opened BoundedInts + import opened Wrappers + import opened Vs = Views.Core + import opened Lx = Lexers.Core + + datatype Split<+T> = SP(t: T, cs: FreshCursor) { + ghost predicate BytesSplitFrom?(cs0: Cursor, spec: T -> bytes) { + cs0.Bytes() == spec(t) + cs.Bytes() + } + + ghost predicate SplitFrom?(cs0: Cursor, spec: T -> bytes) { + cs.SplitFrom?(cs0) && BytesSplitFrom?(cs0, spec) + } + + ghost predicate StrictlySplitFrom?(cs0: Cursor, spec: T -> bytes) { + cs.StrictlySplitFrom?(cs0) && BytesSplitFrom?(cs0, spec) + } + } + + // LATER: Make this a newtype and put members here instead of requiring `Valid?` everywhere + type Cursor = ps: Cursor_ | ps.Valid? + witness Cursor([], 0, 0, 0) + type FreshCursor = ps: Cursor | ps.BOF? + witness Cursor([], 0, 0, 0) + + datatype CursorError<+R> = + | EOF + | ExpectingByte(expected: byte, b: opt_byte) + | ExpectingAnyByte(expected_sq: seq, b: opt_byte) + | OtherError(err: R) + { // TODO: Include positions in errors + function ToString(pr: R -> string) : string { + match this + case EOF => "Reached EOF" + case ExpectingByte(b0, b) => + var c := if b > 0 then "'" + [b as char] + "'" else "EOF"; + "Expecting '" + [b0 as char] + "', read " + c + case ExpectingAnyByte(bs0, b) => + var c := if b > 0 then "'" + [b as char] + "'" else "EOF"; + var c0s := seq(|bs0|, idx requires 0 <= idx < |bs0| => bs0[idx] as char); + "Expecting one of '" + c0s + "', read " + c + case OtherError(err) => pr(err) + } + } + type CursorResult<+R> = Result> + + datatype Cursor_ = Cursor(s: bytes, beg: uint32, point: uint32, end: uint32) { + ghost const Valid?: bool := + 0 <= beg as int <= point as int <= end as int <= |s| < TWO_TO_THE_32 + + const BOF? := + point == beg + + const EOF? := + point == end + + static function OfView(v: View) : FreshCursor { + Cursor(v.s, v.beg, v.beg, v.end) + } + + static function OfBytes(bs: bytes) : FreshCursor + requires |bs| < TWO_TO_THE_32 + { + Cursor(bs, 0, 0, |bs| as uint32) + } + + function Bytes() : bytes + requires Valid? + { + s[beg..end] + } + + ghost predicate StrictlyAdvancedFrom?(other: Cursor): (b: bool) + requires Valid? + ensures b ==> + SuffixLength() < other.SuffixLength() + ensures b ==> + beg == other.beg && end == other.end ==> + forall idx | beg <= idx < point :: s[idx] == other.s[idx] + { + && s == other.s + && beg == other.beg + && end == other.end + && point > other.point + } + + ghost predicate AdvancedFrom?(other: Cursor) + requires Valid? + { + || this == other + || StrictlyAdvancedFrom?(other) + } + + ghost predicate StrictSuffixOf?(other: Cursor) + requires Valid? + ensures StrictSuffixOf?(other) ==> + Length() < other.Length() + { + && s == other.s + && beg > other.beg + && end == other.end + } + + ghost predicate SuffixOf?(other: Cursor) + requires Valid? + { + || this == other + || StrictSuffixOf?(other) + } + + ghost predicate StrictlySplitFrom?(other: Cursor) + requires Valid? + { + && BOF? + && StrictSuffixOf?(other) + } + + ghost predicate SplitFrom?(other: Cursor) + requires Valid? + { + || this == other + || StrictlySplitFrom?(other) + } + + function Prefix() : View requires Valid? { + View(s, beg, point) + } + + function Suffix() : Cursor requires Valid? { + this.(beg := point) + } + + function Split() : (sp: Split) requires Valid? + ensures sp.SplitFrom?(this, (v: View) => v.Bytes()) + ensures beg != point ==> sp.StrictlySplitFrom?(this, (v: View) => v.Bytes()) + ensures !BOF? ==> (sp.StrictlySplitFrom?(this, (v: View) => v.Bytes()) && sp.cs.StrictSuffixOf?(this)) + ensures !EOF? <==> !sp.cs.EOF? + { + SP(this.Prefix(), this.Suffix()) + } + + function PrefixLength() : uint32 requires Valid? { + point - beg + } + + function SuffixLength() : uint32 requires Valid? { + end - point + } + + function Length() : uint32 requires Valid? { + end - beg + } + + lemma PrefixSuffixLength() + requires Valid? + ensures Length() == PrefixLength() + SuffixLength() + {} + + ghost predicate ValidIndex?(idx: uint32) { + beg as int + idx as int < end as int + } + + function At(idx: uint32) : byte + requires Valid? + requires ValidIndex?(idx) + { + s[beg + idx] + } + + ghost predicate ValidSuffixIndex?(idx: uint32) { + point as int + idx as int < end as int + } + + function SuffixAt(idx: uint32) : byte + requires Valid? + requires ValidSuffixIndex?(idx) + { + s[point + idx] + } + + function Peek(): (r: opt_byte) + requires Valid? + ensures r < 0 <==> EOF? + { + if EOF? then -1 + else SuffixAt(0) as opt_byte + } + + predicate LookingAt(c: char): (b: bool) + requires Valid? + requires c as int < 256 + ensures b <==> !EOF? && SuffixAt(0) == c as byte + { + Peek() == c as opt_byte + } + + function Skip(n: uint32): (ps: Cursor) + requires Valid? + requires point as int + n as int <= end as int + ensures n == 0 ==> ps == this + ensures n > 0 ==> ps.StrictlyAdvancedFrom?(this) + { + this.(point := point + n) + } + + function Unskip(n: uint32): Cursor + requires Valid? + requires beg as int <= point as int - n as int + { + this.(point := point - n) + } + + function Get(err: R): (ppr: CursorResult) + requires Valid? + ensures ppr.Success? ==> ppr.value.StrictlyAdvancedFrom?(this) + { + if EOF? then Failure(OtherError(err)) + else Success(Skip(1)) + } + + function AssertByte(b: byte): (pr: CursorResult) + requires Valid? + ensures pr.Success? ==> !EOF? + ensures pr.Success? ==> s[point] == b + ensures pr.Success? ==> pr.value.StrictlyAdvancedFrom?(this) + { + var nxt := Peek(); + if nxt == b as opt_byte then Success(Skip(1)) + else Failure(ExpectingByte(b, nxt)) + } + + function {:tailrecursion} AssertBytes(bs: bytes, offset: uint32 := 0): (pr: CursorResult) + requires Valid? + requires |bs| < TWO_TO_THE_32 + requires offset <= |bs| as uint32 + requires forall b | b in bs :: b as int < 256 + decreases SuffixLength() + ensures pr.Success? ==> pr.value.AdvancedFrom?(this) + ensures pr.Success? && offset < |bs| as uint32 ==> pr.value.StrictlyAdvancedFrom?(this) + ensures pr.Success? ==> s[point..pr.value.point] == bs[offset..] + { + if offset == |bs| as uint32 then Success(this) + else + var ps :- AssertByte(bs[offset] as byte); + ps.AssertBytes(bs, offset + 1) + } + + function AssertChar(c0: char): (pr: CursorResult) + requires Valid? + requires c0 as int < 256 + ensures pr.Success? ==> pr.value.StrictlyAdvancedFrom?(this) + { + AssertByte(c0 as byte) + } + + function SkipByte(): (ps: Cursor) + requires Valid? + decreases SuffixLength() + ensures ps.AdvancedFrom?(this) + ensures !EOF? ==> ps.StrictlyAdvancedFrom?(this) + { + if EOF? then this + else Skip(1) + } + + function SkipIf(p: byte -> bool): (ps: Cursor) + requires Valid? + decreases SuffixLength() + ensures ps.AdvancedFrom?(this) + ensures !EOF? && p(SuffixAt(0)) ==> ps.StrictlyAdvancedFrom?(this) + { + if EOF? || !p(SuffixAt(0)) then this + else Skip(1) + } + + function SkipWhile(p: byte -> bool): (ps: Cursor) + requires Valid? + decreases SuffixLength() + ensures ps.AdvancedFrom?(this) + ensures forall idx | point <= idx < ps.point :: p(ps.s[idx]) + { + if EOF? || !p(SuffixAt(0)) then this + else Skip(1).SkipWhile(p) + } by method { + var point' := this.point; + var end := this.end; + while point' < end && p(this.s[point']) + // BUG(https://github.com/dafny-lang/dafny/issues/4847) + invariant var thisAfter := this.(point := point'); thisAfter.Valid? + invariant var thisAfter := this.(point := point'); thisAfter.SkipWhile(p) == this.SkipWhile(p) + { + point' := point' + 1; + } + return Cursor(this.s, this.beg, point', this.end); + } + + function SkipWhileLexer(step: Lexer, st: A) + : (pr: CursorResult) + requires Valid? + decreases SuffixLength() + ensures pr.Success? ==> pr.value.AdvancedFrom?(this) + { + match step(st, Peek()) + case Accept => Success(this) + case Reject(err) => Failure(OtherError(err)) + case Partial(st) => + if EOF? then Failure(EOF) + else Skip(1).SkipWhileLexer(step, st) + } by method { + var point' := point; + var end := this.end; + var st' := st; + while true + // BUG(https://github.com/dafny-lang/dafny/issues/4847) + invariant var thisAfter := this.(point := point'); thisAfter.Valid? + invariant var thisAfter := this.(point := point'); thisAfter.SkipWhileLexer(step, st') == this.SkipWhileLexer(step, st) + decreases var thisAfter := this.(point := point'); thisAfter.SuffixLength() + { + var eof := point' == end; + var minusone: opt_byte := -1; // BUG(https://github.com/dafny-lang/dafny/issues/2191) + var c := if eof then minusone else this.s[point'] as opt_byte; + match step(st', c) + case Accept => return Success(Cursor(this.s, this.beg, point', this.end)); + case Reject(err) => return Failure(OtherError(err)); + case Partial(st'') => + if eof { return Failure(EOF); } + else { st' := st''; point' := point' + 1; } + } + } + } +} \ No newline at end of file diff --git a/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Utils/Lexers.dfy b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Utils/Lexers.dfy new file mode 100644 index 00000000000..8dde6217946 --- /dev/null +++ b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Utils/Lexers.dfy @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright by the contributors to the Dafny Project + * SPDX-License-Identifier: MIT + *******************************************************************************/ + +/** + Contains a state machine that recognizes quoted strings and skips over backslash-escaped quotes. + */ +module DafnyStdLibs.JSON.Utils.Lexers { + module Core { + import opened Wrappers + import opened BoundedInts + + datatype LexerResult<+T, +R> = + // A Lexer may return three results: + | Accept // The input is valid. + | Reject(err: R) // The input is not valid; `err` says why. + | Partial(st: T) // More input is needed to finish lexing. + + type Lexer = (T, opt_byte) -> LexerResult + } + + module Strings { + import opened Core + import opened BoundedInts + + type StringBodyLexerState = /* escaped: */ bool + const StringBodyLexerStart: StringBodyLexerState := false + + function StringBody(escaped: StringBodyLexerState, byte: opt_byte) + : LexerResult + { + if byte == '\\' as opt_byte then Partial(!escaped) + else if byte == '\"' as opt_byte && !escaped then Accept + else Partial(false) + } + + datatype StringLexerState = Start | Body(escaped: bool) | End + const StringLexerStart: StringLexerState := Start + + function String(st: StringLexerState, byte: opt_byte) + : LexerResult + { + match st + case Start() => + if byte == '\"' as opt_byte then Partial(Body(false)) + else Reject("String must start with double quote") + case End() => + Accept + case Body(escaped) => + if byte == '\\' as opt_byte then Partial(Body(!escaped)) + else if byte == '\"' as opt_byte && !escaped then Partial(End) + else Partial(Body(false)) + } + } +} \ No newline at end of file diff --git a/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Utils/Parsers.dfy b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Utils/Parsers.dfy new file mode 100644 index 00000000000..a44c4d46303 --- /dev/null +++ b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Utils/Parsers.dfy @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright by the contributors to the Dafny Project + * SPDX-License-Identifier: MIT + *******************************************************************************/ + +/** + Contains definitions of well-formedness for parsers (stating that they must consume part of their input). + */ +module DafnyStdLibs.JSON.Utils.Parsers { + import opened BoundedInts + import opened Wrappers + import opened Views.Core + import opened Cursors + + type SplitResult<+T, +R> = Result, CursorError> + + type Parser = p: Parser_ | p.Valid?() + // BUG(https://github.com/dafny-lang/dafny/issues/2103) + witness ParserWitness() // BUG(https://github.com/dafny-lang/dafny/issues/2175) + datatype Parser_ = Parser(fn: FreshCursor -> SplitResult, + ghost spec: T -> bytes) { + ghost predicate Valid?() { + forall cs': FreshCursor :: fn(cs').Success? ==> fn(cs').value.StrictlySplitFrom?(cs', spec) + } + } + + opaque function ParserWitness(): (p: Parser_) + ensures p.Valid?() + { + Parser(_ => Failure(EOF), _ => []) + } + + // BUG(https://github.com/dafny-lang/dafny/issues/2137): It would be much + // nicer if `SubParser` was a special case of `Parser`, but that would require + // making `fn` in parser a partial function `-->`. The problem with that is + // that we would then have to restrict the `Valid?` clause of `Parser` on + // `fn.requires()`, thus making it unprovable in the `SubParser` case (`fn` + // for subparsers is typically a lambda, and the `requires` of lambdas are + // essentially uninformative/opaque). + datatype SubParser_ = SubParser( + ghost cs: Cursor, + ghost pre: FreshCursor -> bool, + fn: FreshCursor --> SplitResult, + ghost spec: T -> bytes) + { + ghost predicate Valid?() { + && (forall cs': FreshCursor | pre(cs') :: fn.requires(cs')) + && (forall cs': FreshCursor | cs'.StrictlySplitFrom?(cs) :: pre(cs')) + && (forall cs': FreshCursor | pre(cs') :: fn(cs').Success? ==> fn(cs').value.StrictlySplitFrom?(cs', spec)) + } + } + + type SubParser = p: SubParser_ | p.Valid?() + witness SubParserWitness() // BUG(https://github.com/dafny-lang/dafny/issues/2175) + + opaque function SubParserWitness(): (subp: SubParser_) + ensures subp.Valid?() + { + SubParser(Cursor([], 0, 0, 0), + (cs: FreshCursor) => false, + (cs: FreshCursor) => Failure(EOF), + _ => []) + } +} \ No newline at end of file diff --git a/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Utils/Views.Writers.dfy b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Utils/Views.Writers.dfy new file mode 100644 index 00000000000..379fda30429 --- /dev/null +++ b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Utils/Views.Writers.dfy @@ -0,0 +1,122 @@ +/******************************************************************************* + * Copyright by the contributors to the Dafny Project + * SPDX-License-Identifier: MIT + *******************************************************************************/ + +/** + Implements slices (`Chain`s) over byte strings whose bounds are representable as `int32` native integers (`View`s). + */ +module DafnyStdLibs.JSON.Utils.Views.Writers { + import opened BoundedInts + import opened Wrappers + import opened Core + + datatype Chain = + | Empty + | Chain(previous: Chain, v: View) + { + function Length() : nat { + if Empty? then 0 + else previous.Length() + v.Length() as int + } + + function Count() : nat { + if Empty? then 0 + else previous.Count() + 1 + } + + function Bytes() : (bs: bytes) + ensures |bs| == Length() + { + if Empty? then [] + else previous.Bytes() + v.Bytes() + } + + function Append(v': View): (c: Chain) + ensures c.Bytes() == Bytes() + v'.Bytes() + { + if Chain? && Adjacent(v, v') then + Chain(previous, Merge(v, v')) + else + Chain(this, v') + } + + method {:tailrecursion} CopyTo(dest: array, end: uint32) + requires end as int == Length() <= dest.Length + modifies dest + ensures dest[..end] == Bytes() + ensures dest[end..] == old(dest[end..]) + { + if Chain? { + var end := end - v.Length(); + v.CopyTo(dest, end); + previous.CopyTo(dest, end); + } + } + } + + type Writer = w: Writer_ | w.Valid? witness Writer(0, Chain.Empty) + datatype Writer_ = Writer(length: uint32, chain: Chain) + { + static const Empty: Writer := Writer(0, Chain.Empty) + + const Empty? := chain.Empty? + const Unsaturated? := length != UINT32_MAX + + ghost function Length() : nat { chain.Length() } + + ghost const Valid? := + length == // length is a saturating counter + if chain.Length() >= TWO_TO_THE_32 then UINT32_MAX + else chain.Length() as uint32 + + function Bytes() : (bs: bytes) + ensures |bs| == Length() + { + chain.Bytes() + } + + static function SaturatedAddU32(a: uint32, b: uint32): uint32 { + if a <= UINT32_MAX - b then a + b + else UINT32_MAX + } + + opaque function Append(v': View): (rw: Writer) + requires Valid? + ensures rw.Unsaturated? <==> v'.Length() < UINT32_MAX - length + ensures rw.Bytes() == Bytes() + v'.Bytes() + { + Writer(SaturatedAddU32(length, v'.Length()), + chain.Append(v')) + } + + function Then(fn: Writer ~> Writer) : Writer + reads fn.reads(this) + requires Valid? + requires fn.requires(this) + { + fn(this) + } + + method {:tailrecursion} CopyTo(dest: array) + requires Valid? + requires Unsaturated? + requires Length() <= dest.Length + modifies dest + ensures dest[..length] == Bytes() + ensures dest[length..] == old(dest[length..]) + { + chain.CopyTo(dest, length); + } + + method ToArray() returns (bs: array) + requires Valid? + requires Unsaturated? + ensures fresh(bs) + ensures bs[..] == Bytes() + { + bs := new byte[length](i => 0); + CopyTo(bs); + } + } +} \ No newline at end of file diff --git a/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Utils/Views.dfy b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Utils/Views.dfy new file mode 100644 index 00000000000..f6c48616d92 --- /dev/null +++ b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Utils/Views.dfy @@ -0,0 +1,127 @@ +/******************************************************************************* + * Copyright by the contributors to the Dafny Project + * SPDX-License-Identifier: MIT + *******************************************************************************/ + +/** + Implements byte strings whose bounds are representable as `int32` native integers (`View`s). + */ +module DafnyStdLibs.JSON.Utils.Views.Core { + import opened BoundedInts + + type View = v: View_ | v.Valid? witness View([], 0, 0) + datatype View_ = View(s: bytes, beg: uint32, end: uint32) { + ghost const Valid?: bool := + 0 <= beg as int <= end as int <= |s| < TWO_TO_THE_32 + + static const Empty: View := + View([], 0, 0) + + const Empty? := + beg == end + + function Length(): uint32 requires Valid? { + end - beg + } + + function Bytes(): bytes requires Valid? { + s[beg..end] + } + + static function OfBytes(bs: bytes) : (v: View) + requires |bs| < TWO_TO_THE_32 + ensures v.Bytes() == bs + { + View(bs, 0 as uint32, |bs| as uint32) + } + + static function OfString(s: string) : bytes + requires forall c: char | c in s :: c as int < 256 + { + seq(|s|, i requires 0 <= i < |s| => + assert s[i] in s; s[i] as byte) + } + + ghost predicate SliceOf?(v': View) { + v'.s == s && v'.beg <= beg && end <= v'.end + } + + ghost predicate StrictPrefixOf?(v': View) { + v'.s == s && v'.beg == beg && end < v'.end + } + + ghost predicate StrictSuffixOf?(v': View) { + v'.s == s && v'.beg < beg && end == v'.end + } + + predicate Byte?(c: byte) + requires Valid? + { + Bytes() == [c] + } by method { + return Length() == 1 && At(0) == c; + } + + predicate Char?(c: char) + requires Valid? + requires c as int < 256 + { + Byte?(c as byte) + } + + ghost predicate ValidIndex?(idx: uint32) { + beg as int + idx as int < end as int + } + + function At(idx: uint32) : byte + requires Valid? + requires ValidIndex?(idx) + { + s[beg + idx] + } + + function Peek(): (r: opt_byte) + requires Valid? + ensures r < 0 <==> Empty? + { + if Empty? then -1 + else At(0) as opt_byte + } + + method CopyTo(dest: array, start: uint32 := 0) + requires Valid? + requires start as int + Length() as int <= dest.Length + requires start as int + Length() as int < TWO_TO_THE_32 + modifies dest + ensures dest[start..start + Length()] == Bytes() + ensures dest[start + Length()..] == old(dest[start + Length()..]) + { + for idx := 0 to Length() + invariant dest[start..start + idx] == Bytes()[..idx] + invariant dest[start + Length()..] == old(dest[start + Length()..]) + { + dest[start + idx] := s[beg + idx]; + } + } + } + + predicate Adjacent(lv: View, rv: View) { + // Compare endpoints first to short-circuit the potentially-costly string + // comparison + && lv.end == rv.beg + // We would prefer to use reference equality here, but doing so in a sound + // way is tricky (see chapter 9 of ‘Verasco: a Formally Verified C Static + // Analyzer’ by Jacques-Henri Jourdan for details). The runtime optimizes + // the common case of physical equality and otherwise performs a length + // check, so the worst case (checking for adjacency in two slices that have + // equal but not physically-equal contents) is hopefully not too common. + && lv.s == rv.s + } + + function Merge(lv: View, rv: View) : (v: View) + requires Adjacent(lv, rv) + ensures v.Bytes() == lv.Bytes() + rv.Bytes() + { + lv.(end := rv.end) + } +} \ No newline at end of file diff --git a/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Values.dfy b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Values.dfy new file mode 100644 index 00000000000..9c3ed7f4e58 --- /dev/null +++ b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/Values.dfy @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright by the contributors to the Dafny Project + * SPDX-License-Identifier: MIT + *******************************************************************************/ + +/** + Defines abstract datatype value trees that represent strings. + */ +module DafnyStdLibs.JSON.Values { + datatype Decimal = + Decimal(n: int, e10: int) // (n) * 10^(e10) + + function Int(n: int): Decimal { + Decimal(n, 0) + } + + datatype JSON = + | Null + | Bool(b: bool) + | String(str: string) + | Number(num: Decimal) + | Object(obj: seq<(string, JSON)>) // Not a map to preserve order + | Array(arr: seq) +} \ No newline at end of file diff --git a/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/ZeroCopy/API.dfy b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/ZeroCopy/API.dfy new file mode 100644 index 00000000000..c57a15580e5 --- /dev/null +++ b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/ZeroCopy/API.dfy @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright by the contributors to the Dafny Project + * SPDX-License-Identifier: MIT + *******************************************************************************/ + +/** + The low-level (zero-copy) API of the JSON library. This version is efficient, verified and allows incremental + changes, but is more cumbersome to use. This API operates on concrete syntax trees that capture details of + punctuation and blanks and represent strings using unescaped, undecoded utf-8 byte sequences. + */ +module DafnyStdLibs.JSON.ZeroCopy.API { + import Grammar + import ConcreteSyntax.Spec + import Serializer + import Deserializer + import opened BoundedInts + import opened Wrappers + import opened Errors + + + /* Serialization (JSON syntax trees to utf-8 bytes) */ + opaque function Serialize(js: Grammar.JSON) : (bs: SerializationResult>) + ensures bs == Success(Spec.JSON(js)) + { + Success(Serializer.Text(js).Bytes()) + } + + method SerializeAlloc(js: Grammar.JSON) returns (bs: SerializationResult>) + ensures bs.Success? ==> fresh(bs.value) + ensures bs.Success? ==> bs.value[..] == Spec.JSON(js) + { + bs := Serializer.Serialize(js); + } + + method SerializeInto(js: Grammar.JSON, bs: array) returns (len: SerializationResult) + modifies bs + ensures len.Success? ==> len.value as int <= bs.Length + ensures len.Success? ==> bs[..len.value] == Spec.JSON(js) + ensures len.Success? ==> bs[len.value..] == old(bs[len.value..]) + ensures len.Failure? ==> unchanged(bs) + { + len := Serializer.SerializeTo(js, bs); + } + + /* Deserialization (utf-8 bytes to JSON syntax trees) */ + opaque function Deserialize(bs: seq) : (js: DeserializationResult) + ensures js.Success? ==> bs == Spec.JSON(js.value) + { + Deserializer.API.OfBytes(bs) + } +} \ No newline at end of file diff --git a/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/ZeroCopy/Deserializer.dfy b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/ZeroCopy/Deserializer.dfy new file mode 100644 index 00000000000..9715231c4b4 --- /dev/null +++ b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/ZeroCopy/Deserializer.dfy @@ -0,0 +1,988 @@ +/******************************************************************************* + * Copyright by the contributors to the Dafny Project + * SPDX-License-Identifier: MIT + *******************************************************************************/ + +/** + Implements low-level (zero-copy) deserialization (utf-8 bytes to JSON syntax trees). + Proves that the deserializer is sound, but not complete w.r.t. the functional specification + defined in `ConcreteSyntax.Spec`: if a value is deserialized successfully, then + re-serializing recovers the original bytestring. + */ +module DafnyStdLibs.JSON.ZeroCopy.Deserializer { + module Core { + import opened BoundedInts + import opened Wrappers + import ConcreteSyntax.Spec + import Vs = Utils.Views.Core + import opened Utils.Cursors + import opened Utils.Parsers + import opened Grammar + import Errors + import opened Collections.Seqs + + + type JSONError = Errors.DeserializationError + type Error = CursorError + type ParseResult<+T> = SplitResult + type Parser = Parsers.Parser + type SubParser = Parsers.SubParser + + // BUG(https://github.com/dafny-lang/dafny/issues/2179) + const SpecView := (v: Vs.View) => Spec.View(v) + + opaque function Get(cs: FreshCursor, err: JSONError): (pr: ParseResult) + ensures pr.Success? ==> pr.value.StrictlySplitFrom?(cs, SpecView) + { + var cs :- cs.Get(err); + Success(cs.Split()) + } + + opaque function WS(cs: FreshCursor): (sp: Split) + ensures sp.SplitFrom?(cs, SpecView) + ensures sp.cs.SuffixOf?(cs) + ensures !cs.BOF? ==> sp.cs.StrictSuffixOf?(cs) + ensures cs.EOF? ==> sp.cs.SuffixOf?(cs.Suffix()) + { + cs.SkipWhile(Blank?).Split() + } by method { + reveal WS(); + var point' := cs.point; + var end := cs.end; + while point' < end && Blank?(cs.s[point']) + // BUG(https://github.com/dafny-lang/dafny/issues/4847) + invariant var csAfter := cs.(point := point'); csAfter.Valid? + invariant var csAfter := cs.(point := point'); csAfter.SkipWhile(Blank?) == cs.SkipWhile(Blank?) + { + point' := point' + 1; + } + return Cursor(cs.s, cs.beg, point', cs.end).Split(); + } + + opaque function {:vcs_split_on_every_assert} {:rlimit 1000000} Structural(cs: FreshCursor, parser: Parser) + : (pr: ParseResult>) + requires forall cs :: parser.fn.requires(cs) + ensures pr.Success? ==> pr.value.StrictlySplitFrom?(cs, st => Spec.Structural(st, parser.spec)) + { + var SP(before, cs) := WS(cs); + var SP(val, cs) :- parser.fn(cs); + var SP(after, cs) := WS(cs); + Success(SP(Grammar.Structural(before, val, after), cs)) + } + + type jopt = v: Vs.View | v.Length() <= 1 witness Vs.View.OfBytes([]) + + function {:rlimit 100000} TryStructural(cs: FreshCursor) + : (sp: Split>) + ensures sp.SplitFrom?(cs, st => Spec.Structural(st, SpecView)) + { + var SP(before, cs) := WS(cs); + var SP(val, cs) := cs.SkipByte().Split(); + var SP(after, cs) := WS(cs); + SP(Grammar.Structural(before, val, after), cs) + } + + ghost predicate ValueParserValid(sp: SubParser) { + forall t :: sp.spec(t) == Spec.Value(t) + } + + type ValueParser = sp: SubParser | ValueParserValid(sp) witness * + } + type Error = Core.Error + + abstract module SequenceParams { + import opened BoundedInts + + import opened Grammar + import opened Utils.Cursors + import opened Core + + const OPEN: byte + const CLOSE: byte + + type TElement + + ghost function ElementSpec(t: TElement): bytes + + function Element(cs: FreshCursor, json: ValueParser) + : (pr: ParseResult) + requires cs.StrictlySplitFrom?(json.cs) + decreases cs.Length() + ensures pr.Success? ==> pr.value.StrictlySplitFrom?(cs, ElementSpec) + } + + abstract module Sequences { + import opened Wrappers + import opened BoundedInts + import opened Params: SequenceParams + + import ConcreteSyntax.SpecProperties + import opened Vs = Utils.Views.Core + import opened Grammar + import opened Utils.Cursors + import Utils.Parsers + import opened Core + + const SEPARATOR: byte := ',' as byte + + type jopen = v: Vs.View | v.Byte?(OPEN) witness Vs.View.OfBytes([OPEN]) + type jclose = v: Vs.View | v.Byte?(CLOSE) witness Vs.View.OfBytes([CLOSE]) + type TBracketed = Bracketed + type TSuffixedElement = Suffixed + + const SpecViewClose: jclose -> bytes := SpecView + const SpecViewOpen: jopen -> bytes := SpecView + + ghost function SuffixedElementSpec(e: TSuffixedElement): bytes { + ElementSpec(e.t) + Spec.CommaSuffix(e.suffix) + } + + ghost function BracketedSpec(ts: TBracketed): bytes { + Spec.Bracketed(ts, SuffixedElementSpec) + } + + ghost function SuffixedElementsSpec(ts: seq): bytes { + Spec.ConcatBytes(ts, SuffixedElementSpec) + } + + opaque function Open(cs: FreshCursor) + : (pr: ParseResult) + ensures pr.Success? ==> pr.value.StrictlySplitFrom?(cs, SpecViewOpen) + { + var cs :- cs.AssertByte(OPEN); + Success(cs.Split()) + } + + opaque function Close(cs: FreshCursor) + : (pr: ParseResult) + ensures pr.Success? ==> pr.value.StrictlySplitFrom?(cs, SpecViewClose) + { + var cs :- cs.AssertByte(CLOSE); + Success(cs.Split()) + } + + opaque function BracketedFromParts(ghost cs: Cursor, + open: Split>, + elems: Split>, + close: Split>) + : (sp: Split) + requires Grammar.NoTrailingSuffix(elems.t) + requires open.StrictlySplitFrom?(cs, c => Spec.Structural(c, SpecView)) + requires elems.SplitFrom?(open.cs, SuffixedElementsSpec) + requires close.StrictlySplitFrom?(elems.cs, c => Spec.Structural(c, SpecView)) + ensures sp.StrictlySplitFrom?(cs, BracketedSpec) + { + var sp := SP(Grammar.Bracketed(open.t, elems.t, close.t), close.cs); + calc { + cs.Bytes(); + Spec.Structural(open.t, SpecView) + open.cs.Bytes(); + { assert open.cs.Bytes() == SuffixedElementsSpec(elems.t) + elems.cs.Bytes(); } + Spec.Structural(open.t, SpecView) + (SuffixedElementsSpec(elems.t) + elems.cs.Bytes()); + { Seqs.LemmaConcatIsAssociative(Spec.Structural(open.t, SpecView), SuffixedElementsSpec(elems.t), elems.cs.Bytes()); } + Spec.Structural(open.t, SpecView) + SuffixedElementsSpec(elems.t) + elems.cs.Bytes(); + { assert elems.cs.Bytes() == Spec.Structural(close.t, SpecView) + close.cs.Bytes(); } + Spec.Structural(open.t, SpecView) + SuffixedElementsSpec(elems.t) + (Spec.Structural(close.t, SpecView) + close.cs.Bytes()); + { Seqs.LemmaConcatIsAssociative(Spec.Structural(open.t, SpecView) + SuffixedElementsSpec(elems.t), Spec.Structural(close.t, SpecView), close.cs.Bytes()); } + Spec.Structural(open.t, SpecView) + SuffixedElementsSpec(elems.t) + Spec.Structural(close.t, SpecView) + close.cs.Bytes(); + Spec.Bracketed(sp.t, SuffixedElementSpec) + close.cs.Bytes(); + } + assert sp.StrictlySplitFrom?(cs, BracketedSpec); + sp + } + + opaque function AppendWithSuffix(ghost cs0: FreshCursor, + ghost json: ValueParser, + elems: Split>, + elem: Split, + sep: Split>) + : (elems': Split>) + requires elems.cs.StrictlySplitFrom?(json.cs) + requires elems.SplitFrom?(cs0, SuffixedElementsSpec) + requires elem.StrictlySplitFrom?(elems.cs, ElementSpec) + requires sep.StrictlySplitFrom?(elem.cs, c => Spec.Structural(c, SpecView)) + requires forall e | e in elems.t :: e.suffix.NonEmpty? + ensures elems'.StrictlySplitFrom?(cs0, SuffixedElementsSpec) + ensures forall e | e in elems'.t :: e.suffix.NonEmpty? + ensures elems'.cs.Length() < elems.cs.Length() + ensures elems'.cs.StrictlySplitFrom?(json.cs) + ensures elems'.SplitFrom?(cs0, SuffixedElementsSpec) + { + var suffixed := Suffixed(elem.t, NonEmpty(sep.t)); + var elems' := SP(elems.t + [suffixed], sep.cs); // DISCUSS: Moving this down doubles the verification time + + assert cs0.Bytes() == SuffixedElementsSpec(elems'.t) + sep.cs.Bytes() by { + assert {:focus} cs0.Bytes() == SuffixedElementsSpec(elems.t) + (ElementSpec(suffixed.t) + Spec.CommaSuffix(suffixed.suffix)) + sep.cs.Bytes() by { + assert cs0.Bytes() == SuffixedElementsSpec(elems.t) + ElementSpec(suffixed.t) + Spec.CommaSuffix(suffixed.suffix) + sep.cs.Bytes() by { + assert cs0.Bytes() == SuffixedElementsSpec(elems.t) + elems.cs.Bytes(); + assert elems.cs.Bytes() == ElementSpec(suffixed.t) + elem.cs.Bytes(); + assert elem.cs.Bytes() == Spec.CommaSuffix(suffixed.suffix) + sep.cs.Bytes(); + Seqs.LemmaConcatIsAssociative(SuffixedElementsSpec(elems.t), ElementSpec(suffixed.t), elem.cs.Bytes()); + Seqs.LemmaConcatIsAssociative(SuffixedElementsSpec(elems.t) + ElementSpec(suffixed.t), Spec.CommaSuffix(suffixed.suffix), sep.cs.Bytes()); + } + Seqs.LemmaConcatIsAssociative(SuffixedElementsSpec(elems.t), ElementSpec(suffixed.t), Spec.CommaSuffix(suffixed.suffix)); + } + assert SuffixedElementsSpec(elems.t) + (ElementSpec(suffixed.t) + Spec.CommaSuffix(suffixed.suffix)) + sep.cs.Bytes() == SuffixedElementsSpec(elems'.t) + sep.cs.Bytes() by { + assert SuffixedElementsSpec(elems.t) + SuffixedElementSpec(suffixed) == SuffixedElementsSpec(elems.t + [suffixed]) by { + SpecProperties.ConcatBytes_Linear(elems.t, [suffixed], SuffixedElementSpec); + assert Spec.ConcatBytes(elems.t, SuffixedElementSpec) + Spec.ConcatBytes([suffixed], SuffixedElementSpec) == Spec.ConcatBytes(elems.t + [suffixed], SuffixedElementSpec); + } + } + } + assert elems'.StrictlySplitFrom?(cs0, SuffixedElementsSpec); + assert forall e | e in elems'.t :: e.suffix.NonEmpty? by { assert elems'.t == elems.t + [suffixed]; } + assert {:split_here} elems'.cs.Length() < elems.cs.Length(); + assert elems'.SplitFrom?(cs0, SuffixedElementsSpec) by { + assert elems'.BytesSplitFrom?(cs0, SuffixedElementsSpec) by { + assert elems'.StrictlySplitFrom?(cs0, SuffixedElementsSpec); + } + assert elems'.cs.SplitFrom?(cs0) by { + assert elems'.cs.StrictlySplitFrom?(cs0) by { + assert elems'.StrictlySplitFrom?(cs0, SuffixedElementsSpec); + } + } + } + elems' + } + + opaque function {:rlimit 10000} {:vcs_split_on_every_assert} AppendLast(ghost cs0: FreshCursor, + ghost json: ValueParser, + elems: Split>, + elem: Split, + sep: Split>) + : (elems': Split>) + requires elems.cs.StrictlySplitFrom?(json.cs) + requires elems.SplitFrom?(cs0, SuffixedElementsSpec) + requires elem.StrictlySplitFrom?(elems.cs, ElementSpec) + requires sep.StrictlySplitFrom?(elem.cs, c => Spec.Structural(c, SpecView)) + requires forall e | e in elems.t :: e.suffix.NonEmpty? + ensures elems'.StrictlySplitFrom?(cs0, SuffixedElementsSpec) + ensures NoTrailingSuffix(elems'.t) + ensures elems'.cs.Length() < elems.cs.Length() + ensures elems'.cs.StrictlySplitFrom?(json.cs) + ensures sep.StrictlySplitFrom?(elems'.cs, c => Spec.Structural(c, SpecView)) + { + var suffixed := Suffixed(elem.t, Empty()); + var elems' := SP(elems.t + [suffixed], elem.cs); + + assert cs0.Bytes() == SuffixedElementsSpec(elems'.t) + elem.cs.Bytes() by { + assert cs0.Bytes() == SuffixedElementsSpec(elems.t) + ElementSpec(suffixed.t) + elem.cs.Bytes() by { + assert elem.t == suffixed.t; + } + assert SuffixedElementsSpec(elems.t) + ElementSpec(suffixed.t) + elem.cs.Bytes() == SuffixedElementsSpec(elems'.t) + elem.cs.Bytes() by { + assert SuffixedElementsSpec(elems.t) + SuffixedElementSpec(suffixed) == SuffixedElementsSpec(elems.t + [suffixed]) by { + SpecProperties.ConcatBytes_Linear(elems.t, [suffixed], SuffixedElementSpec); + assert Spec.ConcatBytes(elems.t, SuffixedElementSpec) + Spec.ConcatBytes([suffixed], SuffixedElementSpec) == Spec.ConcatBytes(elems.t + [suffixed], SuffixedElementSpec); + } + } + } + + assert elems'.StrictlySplitFrom?(cs0, SuffixedElementsSpec); + elems' + } + + lemma {:rlimit 10000} AboutTryStructural(cs: FreshCursor) + ensures + var sp := Core.TryStructural(cs); + var s0 := sp.t.t.Peek(); + && ((!cs.BOF? || !cs.EOF?) && (s0 == SEPARATOR as opt_byte) ==> (var sp: Split> := sp; sp.cs.StrictSuffixOf?(cs))) + && ((s0 == SEPARATOR as opt_byte) ==> var sp: Split> := sp; sp.SplitFrom?(cs, st => Spec.Structural(st, SpecView))) + && ((!cs.BOF? || !cs.EOF?) && (s0 == CLOSE as opt_byte) ==> (var sp: Split> := sp; sp.cs.StrictSuffixOf?(cs))) + && ((s0 == CLOSE as opt_byte) ==> var sp: Split> := sp; sp.SplitFrom?(cs, st => Spec.Structural(st, SpecView))) + { + } + + lemma {:vcs_split_on_every_assert} AboutLists(xs: seq, i: uint32) + requires 0 <= (i as int) < |xs| + ensures xs[(i as int)..(i as int)+1] == [xs[i as int]] + {} + + // The implementation and proof of this function is more painful than + // expected due to the tail recursion. + opaque function {:vcs_split_on_every_assert} {:tailrecursion} Elements( + ghost cs0: FreshCursor, + json: ValueParser, + open: Split>, + elems: Split> + ) // DISCUSS: Why is this function reverified once per instantiation of the module? + : (pr: ParseResult) + requires open.StrictlySplitFrom?(cs0, c => Spec.Structural(c, SpecView)) + requires elems.cs.StrictlySplitFrom?(json.cs) + requires elems.SplitFrom?(open.cs, SuffixedElementsSpec) + requires forall e | e in elems.t :: e.suffix.NonEmpty? + decreases elems.cs.Length() + ensures pr.Success? ==> pr.value.StrictlySplitFrom?(cs0, BracketedSpec) + { + var elem :- Element(elems.cs, json); + if elem.cs.EOF? then + Failure(EOF) + else + AboutTryStructural(elem.cs); + var sep := Core.TryStructural(elem.cs); + var s0 := sep.t.t.Peek(); + if s0 == SEPARATOR as opt_byte && sep.t.t.Length() == 1 then + assert sep.t.t.Char?(',') by { + calc { + sep.t.t.Char?(','); + sep.t.t.Byte?(',' as byte); + sep.t.t.Byte?(SEPARATOR); + sep.t.t.Bytes() == [SEPARATOR]; + sep.t.t.s[(sep.t.t.beg as int)..(sep.t.t.end as int)] == [SEPARATOR]; + { assert (sep.t.t.beg as int) + 1 == (sep.t.t.end as int) by { assert sep.t.t.Length() == 1; } } + sep.t.t.s[(sep.t.t.beg as int)..(sep.t.t.beg as int) + 1] == [SEPARATOR]; + { assert sep.t.t.s[(sep.t.t.beg as int)..(sep.t.t.beg as int) + 1] == [sep.t.t.s[sep.t.t.beg as int]] by { AboutLists(sep.t.t.s, sep.t.t.beg); } } + [sep.t.t.s[sep.t.t.beg as int]] == [SEPARATOR]; + sep.t.t.s[sep.t.t.beg as int] as opt_byte == SEPARATOR as opt_byte; + sep.t.t.At(0) as opt_byte == SEPARATOR as opt_byte; + (s0 == SEPARATOR as opt_byte); + true; + } + } + var sep: Split> := sep; + assert AppendWithSuffix.requires(open.cs, json, elems, elem, sep) by { + assert {:focus} elems.cs.StrictlySplitFrom?(json.cs); + assert elems.SplitFrom?(open.cs, SuffixedElementsSpec); + assert elem.StrictlySplitFrom?(elems.cs, ElementSpec); + assert sep.StrictlySplitFrom?(elem.cs, c => Spec.Structural(c, SpecView)) by { + assert sep.BytesSplitFrom?(elem.cs, c => Spec.Structural(c, SpecView)) by { + assert sep.SplitFrom?(elem.cs, c => Spec.Structural(c, SpecView)); + } + assert sep.cs.StrictlySplitFrom?(elem.cs) by { + assert sep.cs.BOF?; + assert sep.cs.StrictSuffixOf?(elem.cs) by { + assert !elem.cs.EOF?; + } + } + } + assert forall e | e in elems.t :: e.suffix.NonEmpty?; + assert {:split_here} true; + } + var elems := AppendWithSuffix(open.cs, json, elems, elem, sep); + Elements(cs0, json, open, elems) + else if s0 == CLOSE as opt_byte && sep.t.t.Length() == 1 then + assert sep.t.t.Byte?(CLOSE) by { + calc { + sep.t.t.Byte?(CLOSE); + sep.t.t.Bytes() == [CLOSE]; + sep.t.t.s[(sep.t.t.beg as int)..(sep.t.t.end as int)] == [CLOSE]; + { assert (sep.t.t.beg as int) + 1 == (sep.t.t.end as int) by { assert sep.t.t.Length() == 1; } } + sep.t.t.s[(sep.t.t.beg as int)..(sep.t.t.beg as int) + 1] == [CLOSE]; + { assert sep.t.t.s[(sep.t.t.beg as int)..(sep.t.t.beg as int) + 1] == [sep.t.t.s[sep.t.t.beg as int]] by { AboutLists(sep.t.t.s, sep.t.t.beg); } } + [sep.t.t.s[sep.t.t.beg as int]] == [CLOSE]; + sep.t.t.s[sep.t.t.beg as int] as opt_byte == CLOSE as opt_byte; + sep.t.t.At(0) as opt_byte == CLOSE as opt_byte; + (s0 == CLOSE as opt_byte); + true; + } + } + var sep: Split> := sep; + assert AppendLast.requires(open.cs, json, elems, elem, sep) by { + assert elems.cs.StrictlySplitFrom?(json.cs); + assert elems.SplitFrom?(open.cs, SuffixedElementsSpec); + assert elem.StrictlySplitFrom?(elems.cs, ElementSpec); + assert sep.StrictlySplitFrom?(elem.cs, c => Spec.Structural(c, SpecView)) by { + assert sep.BytesSplitFrom?(elem.cs, c => Spec.Structural(c, SpecView)) by { + assert sep.SplitFrom?(elem.cs, c => Spec.Structural(c, SpecView)); + } + assert sep.cs.StrictlySplitFrom?(elem.cs) by { + assert sep.cs.BOF?; + assert sep.cs.StrictSuffixOf?(elem.cs) by { + assert !elem.cs.EOF?; + } + } + } + assert forall e | e in elems.t :: e.suffix.NonEmpty?; + } + var elems' := AppendLast(open.cs, json, elems, elem, sep); + assert elems'.SplitFrom?(open.cs, SuffixedElementsSpec) by { + assert elems'.StrictlySplitFrom?(open.cs, SuffixedElementsSpec); + } + var bracketed := BracketedFromParts(cs0, open, elems', sep); + assert bracketed.StrictlySplitFrom?(cs0, BracketedSpec); + Success(bracketed) + else + var separator := SEPARATOR; + var pr := Failure(ExpectingAnyByte([CLOSE, separator], s0)); + pr + } + + lemma AboutCloseParser() + ensures Parsers.Parser(Close, SpecViewClose).Valid?() + { + assert Parsers.Parser(Close, SpecViewClose).Valid?() by { + forall cs': FreshCursor ensures Close(cs').Success? ==> Close(cs').value.StrictlySplitFrom?(cs', SpecViewClose) { + if Close(cs').Success? { + assert Close(cs').value.StrictlySplitFrom?(cs', SpecViewClose) by { + assert Close(cs').Success? ==> Close(cs').value.StrictlySplitFrom?(cs', SpecViewClose); + } + } + } + } + } + + opaque function {:vcs_split_on_every_assert} Bracketed(cs: FreshCursor, json: ValueParser) + : (pr: ParseResult) + requires cs.SplitFrom?(json.cs) + ensures pr.Success? ==> pr.value.StrictlySplitFrom?(cs, BracketedSpec) + { + var open :- Core.Structural(cs, Parsers.Parser(Open, SpecViewOpen)); + assert open.cs.StrictlySplitFrom?(json.cs); + var elems := SP([], open.cs); + if open.cs.Peek() == CLOSE as opt_byte then + var p := Parsers.Parser(Close, SpecViewClose); + assert p.Valid?() by { + AboutCloseParser(); + } + var close :- Core.Structural(open.cs, p); + Success(BracketedFromParts(cs, open, elems, close)) + else + Elements(cs, json, open, elems) + } + + lemma Valid(x: TBracketed) + ensures x.l.t.Byte?(OPEN) + ensures x.r.t.Byte?(CLOSE) + ensures NoTrailingSuffix(x.data) + ensures forall pf | pf in x.data :: + pf.suffix.NonEmpty? ==> pf.suffix.t.t.Byte?(SEPARATOR) + { // DISCUSS: Why is this lemma needed? Why does it require a body? + var xlt: jopen := x.l.t; + var xrt: jclose := x.r.t; + forall pf | pf in x.data + ensures pf.suffix.NonEmpty? ==> pf.suffix.t.t.Byte?(SEPARATOR) + { + if pf.suffix.NonEmpty? { + var xtt := pf.suffix.t.t; + } + } + } + } + + module API { + import opened BoundedInts + import opened Wrappers + + import opened Vs = Utils.Views.Core + import opened Grammar + import opened Core + import opened Errors + import Utils.Cursors + import Values + + function LiftCursorError(err: Cursors.CursorError): DeserializationError { + match err + case EOF => ReachedEOF + case ExpectingByte(expected, b) => ExpectingByte(expected, b) + case ExpectingAnyByte(expected_sq, b) => ExpectingAnyByte(expected_sq, b) + case OtherError(err) => err + } + + opaque function {:vcs_split_on_every_assert} {:rlimit 10000} JSON(cs: Cursors.FreshCursor) : (pr: DeserializationResult>) + ensures pr.Success? ==> pr.value.StrictlySplitFrom?(cs, Spec.JSON) + { + Core.Structural(cs, Parsers.Parser(Values.Value, Spec.Value)).MapFailure(LiftCursorError) + } + + opaque function Text(v: View) : (jsr: DeserializationResult) + ensures jsr.Success? ==> v.Bytes() == Spec.JSON(jsr.value) + { + var SP(text, cs) :- JSON(Cursors.Cursor.OfView(v)); + assert Cursors.SP(text, cs).BytesSplitFrom?(Cursors.Cursor.OfView(v), Spec.JSON); + assert v.Bytes() == Spec.JSON(text) + cs.Bytes(); + :- Need(cs.EOF?, Errors.ExpectingEOF); + assert cs.Bytes() == []; + Success(text) + } + + opaque function OfBytes(bs: bytes) : (jsr: DeserializationResult) + ensures jsr.Success? ==> bs == Spec.JSON(jsr.value) + { + :- Need(|bs| < TWO_TO_THE_32, Errors.IntOverflow); + Text(Vs.View.OfBytes(bs)) + } + } + + module Values { + import Strings + import Numbers + import Objects + import Arrays + import Constants + import ConcreteSyntax.SpecProperties + import opened BoundedInts + import opened Wrappers + import opened Grammar + import opened Utils.Cursors + import opened Core + + opaque function {:vcs_split_on_every_assert} Value(cs: FreshCursor) : (pr: ParseResult) + decreases cs.Length(), 1 + ensures pr.Success? ==> pr.value.StrictlySplitFrom?(cs, Spec.Value) + { + var c := cs.Peek(); + if c == '{' as opt_byte then + var SP(obj, cs') :- Objects.Object(cs, ValueParser(cs)); + var v := Grammar.Object(obj); + var sp := SP(v, cs'); + assert sp.StrictlySplitFrom?(cs, Spec.Value) by { + Spec.UnfoldValueObject(v); + assert SP(obj, cs').StrictlySplitFrom?(cs, Spec.Object); + } + Spec.UnfoldValueObject(v); + assert sp.StrictlySplitFrom?(cs, Spec.Value); + Success(sp) + else if c == '[' as opt_byte then + var SP(arr, cs') :- Arrays.Array(cs, ValueParser(cs)); + var v := Grammar.Array(arr); + var sp := SP(v, cs'); + assert sp.StrictlySplitFrom?(cs, Spec.Value) by { + assert SP(arr, cs').StrictlySplitFrom?(cs, Spec.Array); + Spec.UnfoldValueArray(v); + } + assert sp.StrictlySplitFrom?(cs, Spec.Value); + Success(sp) + else if c == '\"' as opt_byte then + var SP(str, cs') :- Strings.String(cs); + assert (SP(Grammar.String(str), cs')).StrictlySplitFrom?(cs, Spec.Value) by { + calc { + (SP(Grammar.String(str), cs')).StrictlySplitFrom?(cs, Spec.Value); + cs'.StrictlySplitFrom?(cs) && (SP(Grammar.String(str), cs')).BytesSplitFrom?(cs, Spec.Value); + cs'.StrictlySplitFrom?(cs) && (cs.Bytes() == Spec.Value(Grammar.String(str)) + cs'.Bytes()); + cs'.StrictlySplitFrom?(cs) && (cs.Bytes() == Spec.String(str) + cs'.Bytes()); + cs'.StrictlySplitFrom?(cs) && SP(str, cs').BytesSplitFrom?(cs, Spec.String); + SP(str, cs').StrictlySplitFrom?(cs, Spec.String); + true; + } + } + Success(SP(Grammar.String(str), cs')) + else if c == 't' as opt_byte then + var SP(cst, cs') :- Constants.Constant(cs, TRUE); + assert (SP(Grammar.Bool(cst), cs')).StrictlySplitFrom?(cs, Spec.Value) by { + var f := _ => TRUE; + calc { + (SP(Grammar.Bool(cst), cs')).StrictlySplitFrom?(cs, Spec.Value); + cs'.StrictlySplitFrom?(cs) && (SP(Grammar.Bool(cst), cs')).BytesSplitFrom?(cs, Spec.Value); + cs'.StrictlySplitFrom?(cs) && (cs.Bytes() == Spec.Value(Grammar.Bool(cst)) + cs'.Bytes()); + cs'.StrictlySplitFrom?(cs) && (cs.Bytes() == Spec.View(cst) + cs'.Bytes()); + cs'.StrictlySplitFrom?(cs) && (cs.Bytes() == cst.Bytes() + cs'.Bytes()); + cs'.StrictlySplitFrom?(cs) && (cs.Bytes() == TRUE + cs'.Bytes()); + cs'.StrictlySplitFrom?(cs) && (cs.Bytes() == f(Grammar.Bool(cst)) + cs'.Bytes()); + cs'.StrictlySplitFrom?(cs) && (SP(Grammar.Bool(cst), cs')).BytesSplitFrom?(cs, f); + { assert cs'.StrictlySplitFrom?(cs) <==> cs'.SplitFrom?(cs) by { assert cs' != cs; } } + cs'.SplitFrom?(cs) && (SP(Grammar.Bool(cst), cs')).BytesSplitFrom?(cs, f); + (SP(Grammar.Bool(cst), cs')).SplitFrom?(cs, f); + true; + } + } + Success(SP(Grammar.Bool(cst), cs')) + else if c == 'f' as opt_byte then + var SP(cst, cs') :- Constants.Constant(cs, FALSE); + assert (SP(Grammar.Bool(cst), cs')).StrictlySplitFrom?(cs, Spec.Value) by { + var f := _ => FALSE; + calc { + (SP(Grammar.Bool(cst), cs')).StrictlySplitFrom?(cs, Spec.Value); + cs'.StrictlySplitFrom?(cs) && (SP(Grammar.Bool(cst), cs')).BytesSplitFrom?(cs, Spec.Value); + cs'.StrictlySplitFrom?(cs) && (cs.Bytes() == Spec.Value(Grammar.Bool(cst)) + cs'.Bytes()); + cs'.StrictlySplitFrom?(cs) && (cs.Bytes() == Spec.View(cst) + cs'.Bytes()); + cs'.StrictlySplitFrom?(cs) && (cs.Bytes() == cst.Bytes() + cs'.Bytes()); + cs'.StrictlySplitFrom?(cs) && (cs.Bytes() == FALSE + cs'.Bytes()); + cs'.StrictlySplitFrom?(cs) && (cs.Bytes() == f(Grammar.Bool(cst)) + cs'.Bytes()); + cs'.StrictlySplitFrom?(cs) && (SP(Grammar.Bool(cst), cs')).BytesSplitFrom?(cs, f); + { assert cs'.StrictlySplitFrom?(cs) <==> cs'.SplitFrom?(cs) by { assert cs' != cs; } } + cs'.SplitFrom?(cs) && (SP(Grammar.Bool(cst), cs')).BytesSplitFrom?(cs, f); + (SP(Grammar.Bool(cst), cs')).SplitFrom?(cs, f); + true; + } + } + Success(SP(Grammar.Bool(cst), cs')) + else if c == 'n' as opt_byte then + var SP(cst, cs') :- Constants.Constant(cs, NULL); + assert (SP(Grammar.Null(cst), cs')).StrictlySplitFrom?(cs, Spec.Value) by { + var f := _ => NULL; + calc { + (SP(Grammar.Null(cst), cs')).StrictlySplitFrom?(cs, Spec.Value); + cs'.StrictlySplitFrom?(cs) && (SP(Grammar.Null(cst), cs')).BytesSplitFrom?(cs, Spec.Value); + cs'.StrictlySplitFrom?(cs) && (cs.Bytes() == Spec.Value(Grammar.Null(cst)) + cs'.Bytes()); + cs'.StrictlySplitFrom?(cs) && (cs.Bytes() == Spec.View(cst) + cs'.Bytes()); + cs'.StrictlySplitFrom?(cs) && (cs.Bytes() == cst.Bytes() + cs'.Bytes()); + cs'.StrictlySplitFrom?(cs) && (cs.Bytes() == NULL + cs'.Bytes()); + cs'.StrictlySplitFrom?(cs) && (cs.Bytes() == f(Grammar.Null(cst)) + cs'.Bytes()); + cs'.StrictlySplitFrom?(cs) && (SP(Grammar.Null(cst), cs')).BytesSplitFrom?(cs, f); + { assert cs'.StrictlySplitFrom?(cs) <==> cs'.SplitFrom?(cs) by { assert cs' != cs; } } + cs'.SplitFrom?(cs) && (SP(Grammar.Null(cst), cs')).BytesSplitFrom?(cs, f); + (SP(Grammar.Null(cst), cs')).SplitFrom?(cs, f); + true; + } + } + Success(SP(Grammar.Null(cst), cs')) + else + var SP(num, cs') :- Numbers.Number(cs); + var v := Grammar.Number(num); + var sp := SP(v, cs'); + assert sp.StrictlySplitFrom?(cs, Spec.Value) by { + assert SP(num, cs').StrictlySplitFrom?(cs, Spec.Number); + Spec.UnfoldValueNumber(v); + } + assert sp.StrictlySplitFrom?(cs, Spec.Value); + Success(sp) + } + + opaque function ValueParser(cs: FreshCursor) : (p: ValueParser) + decreases cs.Length(), 0 + ensures cs.SplitFrom?(p.cs) + { + var pre := (ps': FreshCursor) => ps'.Length() < cs.Length(); + var fn := (ps': FreshCursor) requires pre(ps') => Value(ps'); + Parsers.SubParser(cs, pre, fn, Spec.Value) + } + } + + module Constants { + import opened BoundedInts + import opened Wrappers + import opened Grammar + import opened Core + import opened Utils.Cursors + + opaque function Constant(cs: FreshCursor, expected: bytes) : (pr: ParseResult) + requires |expected| < TWO_TO_THE_32 + ensures pr.Success? ==> pr.value.t.Bytes() == expected + ensures pr.Success? ==> pr.value.SplitFrom?(cs, _ => expected) + { + var cs :- cs.AssertBytes(expected); + Success(cs.Split()) + } + } + + module Strings { + import opened Wrappers + import opened BoundedInts + import opened Grammar + import opened Utils.Cursors + import opened LC = Utils.Lexers.Core + import opened Utils.Lexers.Strings + import opened Utils.Parsers + import opened Core + + opaque function StringBody(cs: Cursor): (pr: CursorResult) + ensures pr.Success? ==> pr.value.AdvancedFrom?(cs) + { + cs.SkipWhileLexer(Strings.StringBody, StringBodyLexerStart) + } by method { + reveal StringBody(); + var escaped := false; + for point' := cs.point to cs.end + // BUG(https://github.com/dafny-lang/dafny/issues/4847) + invariant var csAfter := cs.(point := point'); csAfter.Valid? + invariant var csAfter := cs.(point := point'); csAfter.SkipWhileLexer(Strings.StringBody, escaped) == StringBody(cs) + { + var byte := cs.s[point']; + if byte == '\"' as byte && !escaped { + return Success(Cursor(cs.s, cs.beg, point', cs.end)); + } else if byte == '\\' as byte { + escaped := !escaped; + } else { + escaped := false; + } + } + return Failure(EOF); + } + + function Quote(cs: FreshCursor) : (pr: ParseResult) + ensures pr.Success? ==> pr.value.StrictlySplitFrom?(cs, SpecView) + { + var cs :- cs.AssertChar('\"'); + Success(cs.Split()) + } + + opaque function {:rlimit 10000} String(cs: FreshCursor): (pr: ParseResult) + ensures pr.Success? ==> pr.value.StrictlySplitFrom?(cs, Spec.String) + { + var SP(lq, cs) :- Quote(cs); + var contents :- StringBody(cs); + var SP(contents, cs) := contents.Split(); + var SP(rq, cs) :- Quote(cs); + Success(SP(Grammar.JString(lq, contents, rq), cs)) + } + } + + module Numbers { + import opened BoundedInts + import opened Wrappers + import opened Grammar + import opened Utils.Cursors + import opened Core + + opaque function Digits(cs: FreshCursor) : (sp: Split) + ensures sp.SplitFrom?(cs, SpecView) + { + cs.SkipWhile(Digit?).Split() + } + + opaque function NonEmptyDigits(cs: FreshCursor) : (pr: ParseResult) + ensures pr.Success? ==> pr.value.StrictlySplitFrom?(cs, SpecView) + { + var sp := Digits(cs); + if sp.t.Empty? then + Failure(OtherError(Errors.EmptyNumber)) + else + Success(sp) + } + + opaque function NonZeroInt(cs: FreshCursor) : (pr: ParseResult) + requires cs.Peek() != '0' as opt_byte + ensures pr.Success? ==> pr.value.StrictlySplitFrom?(cs, SpecView) + { + NonEmptyDigits(cs) + } + + opaque function OptionalMinus(cs: FreshCursor) : (sp: Split) + ensures sp.SplitFrom?(cs, SpecView) + { + cs.SkipIf(c => c == '-' as byte).Split() + } + + opaque function OptionalSign(cs: FreshCursor) : (sp: Split) + ensures sp.SplitFrom?(cs, SpecView) + { + cs.SkipIf(c => c == '-' as byte || c == '+' as byte).Split() + } + + opaque function TrimmedInt(cs: FreshCursor) : (pr: ParseResult) + ensures pr.Success? ==> pr.value.StrictlySplitFrom?(cs, SpecView) + { + var sp := cs.SkipIf(c => c == '0' as byte).Split(); + if sp.t.Empty? then NonZeroInt(sp.cs) + else Success(sp) + } + + opaque function {:vcs_split_on_every_assert} {:rlimit 100000} Exp(cs: FreshCursor) : (pr: ParseResult>) + ensures pr.Success? ==> pr.value.SplitFrom?(cs, exp => Spec.Maybe(exp, Spec.Exp)) + { + var SP(e, cs) := + cs.SkipIf(c => c == 'e' as byte || c == 'E' as byte).Split(); + if e.Empty? then + Success(SP(Empty(), cs)) + else + assert e.Char?('e') || e.Char?('E'); + var SP(sign, cs) := OptionalSign(cs); + var SP(num, cs) :- NonEmptyDigits(cs); + Success(SP(NonEmpty(JExp(e, sign, num)), cs)) + } + + opaque function Frac(cs: FreshCursor) : (pr: ParseResult>) + ensures pr.Success? ==> pr.value.SplitFrom?(cs, frac => Spec.Maybe(frac, Spec.Frac)) + { + var SP(period, cs) := + cs.SkipIf(c => c == '.' as byte).Split(); + if period.Empty? then + Success(SP(Empty(), cs)) + else + var SP(num, cs) :- NonEmptyDigits(cs); + Success(SP(NonEmpty(JFrac(period, num)), cs)) + } + + opaque function NumberFromParts( + ghost cs: Cursor, + minus: Split, num: Split, + frac: Split>, exp: Split> + ) + : (sp: Split) + requires minus.SplitFrom?(cs, SpecView) + requires num.StrictlySplitFrom?(minus.cs, SpecView) + requires frac.SplitFrom?(num.cs, frac => Spec.Maybe(frac, Spec.Frac)) + requires exp.SplitFrom?(frac.cs, exp => Spec.Maybe(exp, Spec.Exp)) + ensures sp.StrictlySplitFrom?(cs, Spec.Number) + { + var sp := SP(Grammar.JNumber(minus.t, num.t, frac.t, exp.t), exp.cs); + assert cs.Bytes() == Spec.Number(sp.t) + exp.cs.Bytes() by { + assert cs.Bytes() == Spec.View(minus.t) + Spec.View(num.t) + Spec.Maybe(frac.t, Spec.Frac) + Spec.Maybe(exp.t, Spec.Exp) + exp.cs.Bytes() by { + assert cs.Bytes() == Spec.View(minus.t) + minus.cs.Bytes(); + assert minus.cs.Bytes() == Spec.View(num.t) + num.cs.Bytes(); + assert num.cs.Bytes() == Spec.Maybe(frac.t, Spec.Frac) + frac.cs.Bytes(); + assert frac.cs.Bytes() == Spec.Maybe(exp.t, Spec.Exp) + exp.cs.Bytes(); + Seqs.LemmaConcatIsAssociative(Spec.View(minus.t), Spec.View(num.t), num.cs.Bytes()); + Seqs.LemmaConcatIsAssociative(Spec.View(minus.t) + Spec.View(num.t), Spec.Maybe(frac.t, Spec.Frac), frac.cs.Bytes()); + Seqs.LemmaConcatIsAssociative(Spec.View(minus.t) + Spec.View(num.t) + Spec.Maybe(frac.t, Spec.Frac), Spec.Maybe(exp.t, Spec.Exp), exp.cs.Bytes()); + } + } + assert sp.StrictlySplitFrom?(cs, Spec.Number); + sp + } + + opaque function Number(cs: FreshCursor) : (pr: ParseResult) + ensures pr.Success? ==> pr.value.StrictlySplitFrom?(cs, Spec.Number) + { + var minus := OptionalMinus(cs); + var num :- TrimmedInt(minus.cs); + var frac :- Frac(num.cs); + var exp :- Exp(frac.cs); + Success(NumberFromParts(cs, minus, num, frac, exp)) + } + } + + module ArrayParams refines SequenceParams { + import opened Strings + import opened Wrappers + + type TElement = Value + + const OPEN := '[' as byte + const CLOSE := ']' as byte + + function ElementSpec(t: TElement) : bytes { + Spec.Value(t) + } + + opaque function Element(cs: FreshCursor, json: ValueParser) : (pr: ParseResult) + { + json.fn(cs) + } + } + + module Arrays refines Sequences { + import opened Params = ArrayParams + + lemma {:vcs_split_on_every_assert} BracketedToArray(arr: jarray) + ensures Spec.Bracketed(arr, SuffixedElementSpec) == Spec.Array(arr) + { + var rItem := (d: jitem) requires d < arr => Spec.Item(d); + assert Spec.Bracketed(arr, SuffixedElementSpec) == Spec.Bracketed(arr, rItem) by { + assert SpecProperties.Bracketed_Morphism_Requires(arr, SuffixedElementSpec, rItem); + SpecProperties.Bracketed_Morphism(arr, SuffixedElementSpec, rItem); + } + calc { + Spec.Bracketed(arr, SuffixedElementSpec); + Spec.Bracketed(arr, rItem); + Spec.Array(arr); + } + } + + opaque function {:vcs_split_on_every_assert} Array(cs: FreshCursor, json: ValueParser) + : (pr: ParseResult) + requires cs.SplitFrom?(json.cs) + ensures pr.Success? ==> pr.value.StrictlySplitFrom?(cs, Spec.Array) + { + var sp :- Bracketed(cs, json); + assert sp.StrictlySplitFrom?(cs, BracketedSpec); + BracketedToArray(sp.t); + Success(sp) + } + } + + module ObjectParams refines SequenceParams { + import Strings + import opened Wrappers + + type TElement = jKeyValue + + const OPEN := '{' as byte + const CLOSE := '}' as byte + + function Colon(cs: FreshCursor) : (pr: ParseResult) // DISCUSS: Why can't I make this opaque? + ensures pr.Success? ==> pr.value.StrictlySplitFrom?(cs, SpecView) + { + var cs :- cs.AssertChar(':'); + Success(cs.Split()) + } + + opaque function KeyValueFromParts(ghost cs: Cursor, k: Split, + colon: Split>, v: Split) + : (sp: Split) + requires k.StrictlySplitFrom?(cs, Spec.String) + requires colon.StrictlySplitFrom?(k.cs, c => Spec.Structural(c, SpecView)) + requires v.StrictlySplitFrom?(colon.cs, Spec.Value) + ensures sp.StrictlySplitFrom?(cs, ElementSpec) + { + var sp := SP(Grammar.KeyValue(k.t, colon.t, v.t), v.cs); + assert cs.Bytes() == Spec.KeyValue(sp.t) + v.cs.Bytes() by { + assert cs.Bytes() == Spec.String(k.t) + Spec.Structural(colon.t, SpecView) + Spec.Value(v.t) + v.cs.Bytes() by { + assert cs.Bytes() == Spec.String(k.t) + k.cs.Bytes(); + assert k.cs.Bytes() == Spec.Structural(colon.t, SpecView) + colon.cs.Bytes(); + assert colon.cs.Bytes() == Spec.Value(v.t) + v.cs.Bytes(); + Seqs.LemmaConcatIsAssociative(Spec.String(k.t), Spec.Structural(colon.t, SpecView), colon.cs.Bytes()); + Seqs.LemmaConcatIsAssociative(Spec.String(k.t) + Spec.Structural(colon.t, SpecView), Spec.Value(v.t), v.cs.Bytes()); + } + } + assert sp.StrictlySplitFrom?(cs, ElementSpec); + sp + } + + function ElementSpec(t: TElement) : bytes { + Spec.KeyValue(t) + } + + opaque function {:vcs_split_on_every_assert} Element(cs: FreshCursor, json: ValueParser) + : (pr: ParseResult) + { + var k :- Strings.String(cs); + assert k.cs.StrictlySplitFrom?(json.cs); + assert k.StrictlySplitFrom?(cs, Spec.String); + var p := Parsers.Parser(Colon, SpecView); + assert p.Valid?(); + var colon :- Core.Structural(k.cs, p); + assert colon.StrictlySplitFrom?(k.cs, st => Spec.Structural(st, SpecView)); + assert colon.cs.StrictlySplitFrom?(json.cs); + + assert json.fn.requires(colon.cs) by { + assert json.pre(colon.cs) by { + assert colon.cs.StrictlySplitFrom?(json.cs); + assert json.Valid?(); + } + assert json.Valid?(); + } + + var v :- json.fn(colon.cs); + + assert v.StrictlySplitFrom?(colon.cs, Spec.Value) by { + assert v.cs.StrictlySplitFrom?(colon.cs) by { + assert v.StrictlySplitFrom?(colon.cs, json.spec) by { + assert json.Valid?(); + } + } + assert v.BytesSplitFrom?(colon.cs, Spec.Value) by { + calc { + colon.cs.Bytes(); + { assert v.BytesSplitFrom?(colon.cs, json.spec) by { assert json.Valid?(); } } + json.spec(v.t) + v.cs.Bytes(); + { assert json.spec(v.t) == Spec.Value(v.t) by { assert ValueParserValid(json); } } + Spec.Value(v.t) + v.cs.Bytes(); + } + } + } + var kv := KeyValueFromParts(cs, k, colon, v); + Success(kv) + } + } + + module Objects refines Sequences { + import opened Params = ObjectParams + + lemma {:vcs_split_on_every_assert} BracketedToObject(obj: jobject) + ensures Spec.Bracketed(obj, SuffixedElementSpec) == Spec.Object(obj) + { + var rMember := (d: jmember) requires d < obj => Spec.Member(d); + assert Spec.Bracketed(obj, SuffixedElementSpec) == Spec.Bracketed(obj, rMember) by { + assert Spec.Bracketed(obj, SuffixedElementSpec) == Spec.Bracketed(obj, rMember) by { + assert SpecProperties.Bracketed_Morphism_Requires(obj, SuffixedElementSpec, rMember); + SpecProperties.Bracketed_Morphism(obj, SuffixedElementSpec, rMember); + } + } + calc { + Spec.Bracketed(obj, SuffixedElementSpec); + Spec.Bracketed(obj, rMember); + Spec.Object(obj); + } + } + + opaque function {:vcs_split_on_every_assert} {:rlimit 10000} Object(cs: FreshCursor, json: ValueParser) + : (pr: ParseResult) + requires cs.SplitFrom?(json.cs) + ensures pr.Success? ==> pr.value.StrictlySplitFrom?(cs, Spec.Object) + { + var sp :- Bracketed(cs, json); + assert sp.StrictlySplitFrom?(cs, BracketedSpec); + BracketedToObject(sp.t); + Success(sp) + } + } +} \ No newline at end of file diff --git a/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/ZeroCopy/Serializer.dfy b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/ZeroCopy/Serializer.dfy new file mode 100644 index 00000000000..7586b1d1e13 --- /dev/null +++ b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/JSON/ZeroCopy/Serializer.dfy @@ -0,0 +1,550 @@ +/******************************************************************************* + * Copyright by the contributors to the Dafny Project + * SPDX-License-Identifier: MIT + *******************************************************************************/ + +/** + Implements low-level (zero-copy) serialization (JSON syntax trees to utf-8 bytes). + Proves that the serializer is sound and complete w.r.t. the functional specification + defined in `ConcreteSyntax.Spec.dfy`. + */ +module DafnyStdLibs.JSON.ZeroCopy.Serializer { + import ConcreteSyntax.Spec + import ConcreteSyntax.SpecProperties + import opened BoundedInts + import opened Wrappers + import opened Seqs = Collections.Seqs + import opened Errors + import opened Grammar + import opened Utils.Views.Writers + import opened Vs = Utils.Views.Core + + method Serialize(js: JSON) returns (rbs: SerializationResult>) + ensures rbs.Success? ==> fresh(rbs.value) + ensures rbs.Success? ==> rbs.value[..] == Spec.JSON(js) + { + var writer := Text(js); + :- Need(writer.Unsaturated?, OutOfMemory); + var bs := writer.ToArray(); + return Success(bs); + } + + method SerializeTo(js: JSON, dest: array) returns (len: SerializationResult) + modifies dest + ensures len.Success? ==> len.value as int <= dest.Length + ensures len.Success? ==> dest[..len.value] == Spec.JSON(js) + ensures len.Success? ==> dest[len.value..] == old(dest[len.value..]) + ensures len.Failure? ==> unchanged(dest) + { + var writer := Text(js); + :- Need(writer.Unsaturated?, OutOfMemory); + :- Need(writer.length as int <= dest.Length, OutOfMemory); + writer.CopyTo(dest); + return Success(writer.length); + } + + opaque function Text(js: JSON) : (wr: Writer) + ensures wr.Bytes() == Spec.JSON(js) + { + JSON(js) + } + + opaque function JSON(js: JSON, writer: Writer := Writer.Empty) : (wr: Writer) + ensures wr.Bytes() == writer.Bytes() + Spec.JSON(js) + { + Seqs.LemmaConcatIsAssociative2(writer.Bytes(),js.before.Bytes(), Spec.Value(js.t), js.after.Bytes()); + writer + .Append(js.before) + .Then(wr => Value(js.t, wr)) + .Append(js.after) + } + + opaque function {:vcs_split_on_every_assert} Value(v: Grammar.Value, writer: Writer) : (wr: Writer) + decreases v, 4 + ensures wr.Bytes() == writer.Bytes() + Spec.Value(v) + { + match v + case Null(n) => + var wr := writer.Append(n); + wr + case Bool(b) => + var wr := writer.Append(b); + wr + case String(str) => + var wr := String(str, writer); + calc { + wr.Bytes(); + { assert wr == String(v.str, writer); } + writer.Bytes() + Spec.String(v.str); + { assert v.String?; assert v.String? ==> Spec.Value(v) == Spec.String(v.str); } + writer.Bytes() + Spec.Value(v); + } + wr + case Number(num) => + var wr := Number(num, writer); + calc { + wr.Bytes(); + { assert wr == Number(v.num, writer); } + writer.Bytes() + Spec.Number(v.num); + { assert v.Number?; assert v.Number? ==> Spec.Value(v) == Spec.Number(v.num); } + writer.Bytes() + Spec.Value(v); + } + wr + case Object(obj) => + var wr := Object(obj, writer); + calc { + wr.Bytes(); + { assert wr == Object(v.obj, writer); } + writer.Bytes() + Spec.Object(v.obj); + { assert v.Object?; assert v.Object? ==> Spec.Value(v) == Spec.Object(v.obj); } + writer.Bytes() + Spec.Value(v); + } + wr + case Array(arr) => + var wr := Array(arr, writer); + calc { + wr.Bytes(); + { assert wr == Array(v.arr, writer); } + writer.Bytes() + Spec.Array(v.arr); + { assert v.Array?; assert v.Array? ==> Spec.Value(v) == Spec.Array(v.arr); } + writer.Bytes() + Spec.Value(v); + } + wr + } + + opaque function String(str: jstring, writer: Writer) : (wr: Writer) + decreases str, 0 + ensures wr.Bytes() == writer.Bytes() + Spec.String(str) + { + writer + .Append(str.lq) + .Append(str.contents) + .Append(str.rq) + } + + lemma {:vcs_split_on_every_assert} NumberHelper1(num: jnumber, writer: Writer) + ensures + if num.exp.NonEmpty? then ( + if num.frac.NonEmpty? then + writer.Append(num.minus).Append(num.num).Append(num.frac.t.period).Append(num.frac.t.num).Append(num.exp.t.e).Append(num.exp.t.sign).Append(num.exp.t.num).Bytes() == writer.Bytes() + num.minus.Bytes() + num.num.Bytes() + num.frac.t.period.Bytes() + num.frac.t.num.Bytes() + num.exp.t.e.Bytes() + num.exp.t.sign.Bytes() + num.exp.t.num.Bytes() + else + writer.Append(num.minus).Append(num.num).Append(num.exp.t.e).Append(num.exp.t.sign).Append(num.exp.t.num).Bytes() == writer.Bytes() + num.minus.Bytes() + num.num.Bytes() + num.exp.t.e.Bytes() + num.exp.t.sign.Bytes() + num.exp.t.num.Bytes() + ) else ( + if num.frac.NonEmpty? then + writer.Append(num.minus).Append(num.num).Append(num.frac.t.period).Append(num.frac.t.num).Bytes() == writer.Bytes() + num.minus.Bytes() + num.num.Bytes() + num.frac.t.period.Bytes() + num.frac.t.num.Bytes() + else + writer.Append(num.minus).Append(num.num).Bytes() == writer.Bytes() + num.minus.Bytes() + num.num.Bytes() + ) + { + if num.exp.NonEmpty? { + if num.frac.NonEmpty? { + assert writer.Append(num.minus).Append(num.num).Append(num.frac.t.period).Append(num.frac.t.num).Append(num.exp.t.e).Append(num.exp.t.sign).Append(num.exp.t.num).Bytes() == writer.Bytes() + num.minus.Bytes() + num.num.Bytes() + num.frac.t.period.Bytes() + num.frac.t.num.Bytes() + num.exp.t.e.Bytes() + num.exp.t.sign.Bytes() + num.exp.t.num.Bytes(); + } else { + assert writer.Append(num.minus).Append(num.num).Append(num.exp.t.e).Append(num.exp.t.sign).Append(num.exp.t.num).Bytes() == writer.Bytes() + num.minus.Bytes() + num.num.Bytes() + num.exp.t.e.Bytes() + num.exp.t.sign.Bytes() + num.exp.t.num.Bytes(); + } + } else { + if num.frac.NonEmpty? { + assert writer.Append(num.minus).Append(num.num).Append(num.frac.t.period).Append(num.frac.t.num).Bytes() == writer.Bytes() + num.minus.Bytes() + num.num.Bytes() + num.frac.t.period.Bytes() + num.frac.t.num.Bytes(); + } else { + assert writer.Append(num.minus).Append(num.num).Bytes() == writer.Bytes() + num.minus.Bytes() + num.num.Bytes(); + } + } + } + + lemma {:vcs_split_on_every_assert} NumberHelper2a(num: jnumber, writer: Writer) + ensures Spec.Number(num) == num.minus.Bytes() + num.num.Bytes() + Spec.Maybe(num.frac, Spec.Frac) + Spec.Maybe(num.exp, Spec.Exp) + {} + + lemma {:vcs_split_on_every_assert} {:rlimit 10000} NumberHelper2(num: jnumber, writer: Writer) + ensures + if num.exp.NonEmpty? then ( + if num.frac.NonEmpty? then writer.Bytes() + Spec.Number(num) == writer.Bytes() + num.minus.Bytes() + num.num.Bytes() + num.frac.t.period.Bytes() + num.frac.t.num.Bytes() + num.exp.t.e.Bytes() + num.exp.t.sign.Bytes() + num.exp.t.num.Bytes() else writer.Bytes() + Spec.Number(num) == writer.Bytes() + num.minus.Bytes() + num.num.Bytes() + num.exp.t.e.Bytes() + num.exp.t.sign.Bytes() + num.exp.t.num.Bytes() + ) else ( + if num.frac.NonEmpty? then writer.Bytes() + Spec.Number(num) == writer.Bytes() + num.minus.Bytes() + num.num.Bytes() + num.frac.t.period.Bytes() + num.frac.t.num.Bytes() else writer.Bytes() + Spec.Number(num) == writer.Bytes() + num.minus.Bytes() + num.num.Bytes() + ) + { + if num.exp.NonEmpty? { + if num.frac.NonEmpty? { + calc { + writer.Bytes() + Spec.Number(num); + { NumberHelper2a(num, writer); } + writer.Bytes() + (num.minus.Bytes() + num.num.Bytes() + Spec.Maybe(num.frac, Spec.Frac) + Spec.Maybe(num.exp, Spec.Exp)); + writer.Bytes() + num.minus.Bytes() + num.num.Bytes() + (Spec.Maybe(num.frac, Spec.Frac) + Spec.Maybe(num.exp, Spec.Exp)); + { assert Spec.Maybe(num.frac, Spec.Frac) == Spec.Frac(num.frac.t); assert Spec.Maybe(num.exp, Spec.Exp) == Spec.Exp(num.exp.t); } + writer.Bytes() + num.minus.Bytes() + num.num.Bytes() + (Spec.Frac(num.frac.t) + Spec.Exp(num.exp.t)); + { assert Spec.Frac(num.frac.t) == num.frac.t.period.Bytes() + num.frac.t.num.Bytes(); assert Spec.Exp(num.exp.t) == num.exp.t.e.Bytes() + num.exp.t.sign.Bytes() + num.exp.t.num.Bytes(); } + writer.Bytes() + num.minus.Bytes() + num.num.Bytes() + ((num.frac.t.period.Bytes() + num.frac.t.num.Bytes()) + (num.exp.t.e.Bytes() + num.exp.t.sign.Bytes() + num.exp.t.num.Bytes())); + writer.Bytes() + num.minus.Bytes() + num.num.Bytes() + num.frac.t.period.Bytes() + num.frac.t.num.Bytes() + num.exp.t.e.Bytes() + num.exp.t.sign.Bytes() + num.exp.t.num.Bytes(); + } + } else { + calc { + writer.Bytes() + Spec.Number(num); + { NumberHelper2a(num, writer); } + writer.Bytes() + (num.minus.Bytes() + num.num.Bytes() + Spec.Maybe(num.frac, Spec.Frac) + Spec.Maybe(num.exp, Spec.Exp)); + writer.Bytes() + num.minus.Bytes() + num.num.Bytes() + (Spec.Maybe(num.frac, Spec.Frac) + Spec.Maybe(num.exp, Spec.Exp)); + { assert Spec.Maybe(num.frac, Spec.Frac) == []; assert Spec.Maybe(num.exp, Spec.Exp) == Spec.Exp(num.exp.t); } + writer.Bytes() + num.minus.Bytes() + num.num.Bytes() + ([] + Spec.Exp(num.exp.t)); + { assert [] + Spec.Exp(num.exp.t) == Spec.Exp(num.exp.t); } + writer.Bytes() + num.minus.Bytes() + num.num.Bytes() + Spec.Exp(num.exp.t); + { assert Spec.Exp(num.exp.t) == num.exp.t.e.Bytes() + num.exp.t.sign.Bytes() + num.exp.t.num.Bytes(); } + writer.Bytes() + num.minus.Bytes() + num.num.Bytes() + (num.exp.t.e.Bytes() + num.exp.t.sign.Bytes() + num.exp.t.num.Bytes()); + writer.Bytes() + num.minus.Bytes() + num.num.Bytes() + num.exp.t.e.Bytes() + num.exp.t.sign.Bytes() + num.exp.t.num.Bytes(); + } + } + } else { + if num.frac.NonEmpty? { + calc { + writer.Bytes() + Spec.Number(num); + { NumberHelper2a(num, writer); } + writer.Bytes() + (num.minus.Bytes() + num.num.Bytes() + Spec.Maybe(num.frac, Spec.Frac) + Spec.Maybe(num.exp, Spec.Exp)); + writer.Bytes() + num.minus.Bytes() + num.num.Bytes() + (Spec.Maybe(num.frac, Spec.Frac) + Spec.Maybe(num.exp, Spec.Exp)); + { assert Spec.Maybe(num.exp, Spec.Exp) == []; assert Spec.Maybe(num.frac, Spec.Frac) == Spec.Frac(num.frac.t); } + writer.Bytes() + num.minus.Bytes() + num.num.Bytes() + (Spec.Frac(num.frac.t) + []); + writer.Bytes() + num.minus.Bytes() + num.num.Bytes() + Spec.Frac(num.frac.t); + { assert Spec.Frac(num.frac.t) == num.frac.t.period.Bytes() + num.frac.t.num.Bytes(); } + writer.Bytes() + num.minus.Bytes() + num.num.Bytes() + num.frac.t.period.Bytes() + num.frac.t.num.Bytes(); + } + } else { + calc { + writer.Bytes() + Spec.Number(num); + { NumberHelper2a(num, writer); } + writer.Bytes() + (num.minus.Bytes() + num.num.Bytes() + Spec.Maybe(num.frac, Spec.Frac) + Spec.Maybe(num.exp, Spec.Exp)); + { assert Spec.Maybe(num.frac, Spec.Frac) == []; assert Spec.Maybe(num.exp, Spec.Exp) == []; } + writer.Bytes() + num.minus.Bytes() + num.num.Bytes() + [] + []; + writer.Bytes() + num.minus.Bytes() + num.num.Bytes(); + } + } + } + } + + opaque function {:vcs_split_on_every_assert} Number(num: jnumber, writer: Writer) : (wr: Writer) + decreases num, 0 + ensures wr.Bytes() == writer.Bytes() + Spec.Number(num) + { + var wr1 := writer.Append(num.minus).Append(num.num); + var wr2 := if num.frac.NonEmpty? then + wr1.Append(num.frac.t.period).Append(num.frac.t.num) + else wr1; + var wr3 := if num.exp.NonEmpty? then + wr2.Append(num.exp.t.e).Append(num.exp.t.sign).Append(num.exp.t.num) + else wr2; + var wr := wr3; + + calc { + wr.Bytes(); + { assert wr == wr3; } + wr3.Bytes(); + { assert wr3 == if num.exp.NonEmpty? then wr2.Append(num.exp.t.e).Append(num.exp.t.sign).Append(num.exp.t.num) else wr2; } + if num.exp.NonEmpty? then wr2.Append(num.exp.t.e).Append(num.exp.t.sign).Append(num.exp.t.num).Bytes() else wr2.Bytes(); + { assert wr2 == if num.frac.NonEmpty? then wr1.Append(num.frac.t.period).Append(num.frac.t.num) else wr1; } + if num.exp.NonEmpty? then ( + if num.frac.NonEmpty? then wr1.Append(num.frac.t.period).Append(num.frac.t.num).Append(num.exp.t.e).Append(num.exp.t.sign).Append(num.exp.t.num).Bytes() else wr1.Append(num.exp.t.e).Append(num.exp.t.sign).Append(num.exp.t.num).Bytes() + ) else ( + if num.frac.NonEmpty? then wr1.Append(num.frac.t.period).Append(num.frac.t.num).Bytes() else wr1.Bytes() + ); + { assert wr1 == writer.Append(num.minus).Append(num.num); } + if num.exp.NonEmpty? then ( + if num.frac.NonEmpty? then writer.Append(num.minus).Append(num.num).Append(num.frac.t.period).Append(num.frac.t.num).Append(num.exp.t.e).Append(num.exp.t.sign).Append(num.exp.t.num).Bytes() else writer.Append(num.minus).Append(num.num).Append(num.exp.t.e).Append(num.exp.t.sign).Append(num.exp.t.num).Bytes() + ) else ( + if num.frac.NonEmpty? then writer.Append(num.minus).Append(num.num).Append(num.frac.t.period).Append(num.frac.t.num).Bytes() else writer.Append(num.minus).Append(num.num).Bytes() + ); + { NumberHelper1(num, writer); } + if num.exp.NonEmpty? then ( + if num.frac.NonEmpty? then writer.Bytes() + num.minus.Bytes() + num.num.Bytes() + num.frac.t.period.Bytes() + num.frac.t.num.Bytes() + num.exp.t.e.Bytes() + num.exp.t.sign.Bytes() + num.exp.t.num.Bytes() else writer.Bytes() + num.minus.Bytes() + num.num.Bytes() + num.exp.t.e.Bytes() + num.exp.t.sign.Bytes() + num.exp.t.num.Bytes() + ) else ( + if num.frac.NonEmpty? then writer.Bytes() + num.minus.Bytes() + num.num.Bytes() + num.frac.t.period.Bytes() + num.frac.t.num.Bytes() else writer.Bytes() + num.minus.Bytes() + num.num.Bytes() + ); + { NumberHelper2(num, writer); } + if num.exp.NonEmpty? then ( + if num.frac.NonEmpty? then writer.Bytes() + Spec.Number(num) else writer.Bytes() + Spec.Number(num) + ) else ( + if num.frac.NonEmpty? then writer.Bytes() + Spec.Number(num) else writer.Bytes() + Spec.Number(num) + ); + writer.Bytes() + Spec.Number(num); + } + wr + } + + // DISCUSS: Can't be opaque, due to the lambda + function {:vcs_split_on_every_assert} StructuralView(st: Structural, writer: Writer) : (wr: Writer) + ensures wr.Bytes() == writer.Bytes() + Spec.Structural(st, Spec.View) + { + writer.Append(st.before).Append(st.t).Append(st.after) + } + + lemma StructuralViewEns(st: Structural, writer: Writer) + ensures StructuralView(st, writer).Bytes() == writer.Bytes() + Spec.Structural(st, Spec.View) + {} + + // FIXME refactor below to merge + lemma BracketedToObject(obj: jobject) + ensures Spec.Bracketed(obj, Spec.Member) == Spec.Object(obj) + { + var rMember := (d: jmember) requires d < obj => Spec.Member(d); + assert Spec.Bracketed(obj, Spec.Member) == Spec.Bracketed(obj, rMember) by { + // We call ``ConcatBytes`` with ``Spec.Member``, whereas the spec calls it + // with ``(d: jmember) requires d in obj.data => Spec.Member(d)``. That's + // why we need an explicit cast, which is performed by the lemma below. + assert SpecProperties.Bracketed_Morphism_Requires(obj, Spec.Member, rMember); + SpecProperties.Bracketed_Morphism(obj, Spec.Member, rMember); + } + calc { + Spec.Bracketed(obj, Spec.Member); + Spec.Bracketed(obj, rMember); + Spec.Object(obj); + } + } + + opaque function Object(obj: jobject, writer: Writer) : (wr: Writer) + decreases obj, 3 + ensures wr.Bytes() == writer.Bytes() + Spec.Object(obj) + { + var wr := StructuralView(obj.l, writer); + StructuralViewEns(obj.l, writer); + var wr := Members(obj, wr); + var wr := StructuralView(obj.r, wr); + Seqs.LemmaConcatIsAssociative2(writer.Bytes(), Spec.Structural(obj.l, Spec.View), Spec.ConcatBytes(obj.data, Spec.Member), Spec.Structural(obj.r, Spec.View)); + assert wr.Bytes() == writer.Bytes() + Spec.Bracketed(obj, Spec.Member); + assert Spec.Bracketed(obj, Spec.Member) == Spec.Object(obj) by { BracketedToObject(obj); } + wr + } + + lemma BracketedToArray(arr: jarray) + ensures Spec.Bracketed(arr, Spec.Item) == Spec.Array(arr) + { + var rItem := (d: jitem) requires d < arr => Spec.Item(d); + assert Spec.Bracketed(arr, Spec.Item) == Spec.Bracketed(arr, rItem) by { + assert SpecProperties.Bracketed_Morphism_Requires(arr, Spec.Item, rItem); + SpecProperties.Bracketed_Morphism(arr, Spec.Item, rItem); + } + calc { + Spec.Bracketed(arr, Spec.Item); + Spec.Bracketed(arr, rItem); + Spec.Array(arr); + } + } + + opaque function Array(arr: jarray, writer: Writer) : (wr: Writer) + decreases arr, 3 + ensures wr.Bytes() == writer.Bytes() + Spec.Array(arr) + { + var wr := StructuralView(arr.l, writer); + StructuralViewEns(arr.l, writer); + var wr := Items(arr, wr); + var wr := StructuralView(arr.r, wr); + Seqs.LemmaConcatIsAssociative2(writer.Bytes(), Spec.Structural(arr.l, Spec.View), Spec.ConcatBytes(arr.data, Spec.Item), Spec.Structural(arr.r, Spec.View)); + assert wr.Bytes() == writer.Bytes() + Spec.Bracketed(arr, Spec.Item); + assert Spec.Bracketed(arr, Spec.Item) == Spec.Array(arr) by { BracketedToArray(arr); } + wr + } + + opaque function Members(obj: jobject, writer: Writer) : (wr: Writer) + decreases obj, 2 + ensures wr.Bytes() == writer.Bytes() + Spec.ConcatBytes(obj.data, Spec.Member) + { + MembersSpec(obj, obj.data, writer) + } by method { + assume {:axiom} false; // BUG(https://github.com/dafny-lang/dafny/issues/2180) + wr := MembersImpl(obj, writer); + } + + opaque function Items(arr: jarray, writer: Writer) : (wr: Writer) + decreases arr, 2 + ensures wr.Bytes() == writer.Bytes() + Spec.ConcatBytes(arr.data, Spec.Item) + { + ItemsSpec(arr, arr.data, writer) + } by method { + assume {:axiom} false; // BUG(https://github.com/dafny-lang/dafny/issues/2180) + wr := ItemsImpl(arr, writer); + } + + ghost function MembersSpec(obj: jobject, members: seq, writer: Writer) : (wr: Writer) + requires forall j | 0 <= j < |members| :: members[j] < obj + decreases obj, 1, members + ensures wr.Bytes() == writer.Bytes() + Spec.ConcatBytes(members, Spec.Member) + { // TR elimination doesn't work for mutually recursive methods, so this + // function is only used as a spec for Members. + if members == [] then writer + else + var butLast, last := members[..|members|-1], members[|members|-1]; + assert members == butLast + [last]; + var wr := MembersSpec(obj, butLast, writer); + var wr := Member(obj, last, wr); + assert wr.Bytes() == writer.Bytes() + (Spec.ConcatBytes(butLast, Spec.Member) + Spec.ConcatBytes([last], Spec.Member)) by { + Seqs.LemmaConcatIsAssociative(writer.Bytes(), Spec.ConcatBytes(butLast, Spec.Member), Spec.ConcatBytes([last], Spec.Member)); + } + SpecProperties.ConcatBytes_Linear(butLast, [last], Spec.Member); + wr + } // No by method block here, because the loop invariant in the method version + // needs to call MembersSpec and the termination checker gets confused by + // that. Instead, see Members above. // DISCUSS + + // DISCUSS: Is there a way to avoid passing the ghost `v` around while + // maintaining the termination argument? Maybe the lambda for elements will be enough? + ghost predicate SequenceSpecRequiresHelper(v: Value, items: seq, + spec: T -> bytes, impl: (Value, T, Writer) --> Writer, + writer: Writer, item: T, wr: Writer) + requires item in items + { + && impl.requires(v, item, wr) + && impl(v, item, wr).Bytes() == wr.Bytes() + spec(item) + } + + ghost predicate SequenceSpecRequires(v: Value, items: seq, + spec: T -> bytes, impl: (Value, T, Writer) --> Writer, + writer: Writer) { + forall item, wr | item in items :: SequenceSpecRequiresHelper(v, items, spec, impl, writer, item, wr) + } + + + ghost function {:vcs_split_on_every_assert} SequenceSpec(v: Value, items: seq, + spec: T -> bytes, impl: (Value, T, Writer) --> Writer, + writer: Writer) + : (wr: Writer) + requires SequenceSpecRequires(v, items, spec, impl, writer) + decreases v, 1, items + ensures wr.Bytes() == writer.Bytes() + Spec.ConcatBytes(items, spec) + { // TR elimination doesn't work for mutually recursive methods, so this + // function is only used as a spec for Items. + if items == [] then writer + else + assert SequenceSpecRequires(v, items[..|items|-1], spec, impl, writer) by { + assert forall item, wr {:trigger SequenceSpecRequiresHelper(v, items[..|items|-1], spec, impl, writer, item, wr)} | item in items[..|items|-1] :: SequenceSpecRequiresHelper(v, items[..|items|-1], spec, impl, writer, item, wr) by { + forall item, wr {:trigger SequenceSpecRequiresHelper(v, items[..|items|-1], spec, impl, writer, item, wr)} | item in items[..|items|-1] ensures SequenceSpecRequiresHelper(v, items[..|items|-1], spec, impl, writer, item, wr) { + assert item in items; + assert SequenceSpecRequiresHelper(v, items, spec, impl, writer, item, wr); + } + } + } + var writer' := SequenceSpec(v, items[..|items|-1], spec, impl, writer); + assert impl.requires(v, items[|items|-1], writer') by { + assert SequenceSpecRequiresHelper(v, items, spec, impl, writer, items[|items|-1], writer') by { + assert SequenceSpecRequires(v, items, spec, impl, writer); + assert items[|items|-1] in items; + } + } + var wr := impl(v, items[|items|-1], writer'); + assert wr.Bytes() == writer.Bytes() + Spec.ConcatBytes(items, spec) by { + calc { + wr.Bytes(); + { assert wr == impl(v, items[|items|-1], writer'); } + impl(v, items[|items|-1], writer').Bytes(); + { assert SequenceSpecRequires(v, items, spec, impl, writer); assert items[|items|-1] in items; assert SequenceSpecRequiresHelper(v, items, spec, impl, writer, items[|items|-1], writer'); } + writer'.Bytes() + spec(items[|items|-1]); + { assert writer' == SequenceSpec(v, items[..|items|-1], spec, impl, writer); } + writer.Bytes() + Spec.ConcatBytes(items[..|items|-1], spec) + spec(items[|items|-1]); + { assert spec(items[|items|-1]) == Spec.ConcatBytes([items[|items|-1]], spec); } + writer.Bytes() + Spec.ConcatBytes(items[..|items|-1], spec) + Spec.ConcatBytes([items[|items|-1]], spec); + { SpecProperties.ConcatBytes_Linear(items[..|items|-1], [items[|items|-1]], spec); } + writer.Bytes() + Spec.ConcatBytes(items[..|items|-1] + [items[|items|-1]], spec); + { assert items == items[..|items|-1] + [items[|items|-1]]; } + writer.Bytes() + Spec.ConcatBytes(items, spec); + } + } + wr + } // No by method block here, because the loop invariant in the method version + // needs to call `SequenceSpec` and the termination checker gets confused by + // that. Instead, see `Sequence`Items above. // DISCUSS + + + ghost function ItemsSpec(arr: jarray, items: seq, writer: Writer) : (wr: Writer) + requires forall j | 0 <= j < |items| :: items[j] < arr + decreases arr, 1, items + ensures wr.Bytes() == writer.Bytes() + Spec.ConcatBytes(items, Spec.Item) + { // TR elimination doesn't work for mutually recursive methods, so this + // function is only used as a spec for Items. + if items == [] then writer + else + var butLast, last := items[..|items|-1], items[|items|-1]; + assert items == butLast + [last]; + var wr := ItemsSpec(arr, butLast, writer); + var wr := Item(arr, last, wr); + assert wr.Bytes() == writer.Bytes() + (Spec.ConcatBytes(butLast, Spec.Item) + Spec.ConcatBytes([last], Spec.Item)) by { + Seqs.LemmaConcatIsAssociative(writer.Bytes(), Spec.ConcatBytes(butLast, Spec.Item), Spec.ConcatBytes([last], Spec.Item)); + } + SpecProperties.ConcatBytes_Linear(butLast, [last], Spec.Item); + wr + } // No by method block here, because the loop invariant in the method version + // needs to call ItemsSpec and the termination checker gets confused by + // that. Instead, see Items above. // DISCUSS + + method {:rlimit 10000} MembersImpl(obj: jobject, writer: Writer) returns (wr: Writer) + decreases obj, 1 + ensures wr == MembersSpec(obj, obj.data, writer) + { + wr := writer; + var members := obj.data; + assert wr == MembersSpec(obj, members[..0], writer); + for i := 0 to |members| // FIXME uint32 + invariant wr == MembersSpec(obj, members[..i], writer) + { + assert members[..i+1][..i] == members[..i]; + wr := Member(obj, members[i], wr); + } + assert members[..|members|] == members; + assert wr == MembersSpec(obj, members, writer); + } + + method {:vcs_split_on_every_assert} ItemsImpl(arr: jarray, writer: Writer) returns (wr: Writer) + decreases arr, 1 + ensures wr == ItemsSpec(arr, arr.data, writer) + { + wr := writer; + var items := arr.data; + assert wr == ItemsSpec(arr, items[..0], writer); + for i := 0 to |items| // FIXME uint32 + invariant wr == ItemsSpec(arr, items[..i], writer) + { + assert items[..i+1][..i] == items[..i] by { + AboutList(items, i, i+1); + } + wr := Item(arr, items[i], wr); + } + assert items[..|items|] == items; + } + + lemma AboutList(xs: seq, i: nat, j: nat) + requires i < j <= |xs| + ensures xs[..j][..i] == xs[..i] + {} + + opaque function Member(ghost obj: jobject, m: jmember, writer: Writer) : (wr: Writer) + requires m < obj + decreases obj, 0 + ensures wr.Bytes() == writer.Bytes() + Spec.Member(m) + { + var wr := String(m.t.k, writer); + var wr := StructuralView(m.t.colon, wr); + var wr := Value(m.t.v, wr); + assert wr.Bytes() == writer.Bytes() + (Spec.String(m.t.k) + Spec.Structural(m.t.colon, Spec.View) + Spec.Value(m.t.v)) by { + Seqs.LemmaConcatIsAssociative2( writer.Bytes(), Spec.String(m.t.k), Spec.Structural(m.t.colon, Spec.View), Spec.Value(m.t.v)); + } + var wr := if m.suffix.Empty? then wr else StructuralView(m.suffix.t, wr); + assert wr.Bytes() == writer.Bytes() + Spec.KeyValue(m.t) + Spec.CommaSuffix(m.suffix) by { + if m.suffix.Empty? { + EmptySequenceIsRightIdentity(Spec.KeyValue(m.t)); + Seqs.LemmaConcatIsAssociative(writer.Bytes(), Spec.KeyValue(m.t), []); + } + else { + assert Spec.StructuralView(m.suffix.t) == Spec.CommaSuffix(m.suffix); + } + } + assert wr.Bytes() == writer.Bytes() + (Spec.KeyValue(m.t) + Spec.CommaSuffix(m.suffix)) by { + Seqs.LemmaConcatIsAssociative(writer.Bytes(), Spec.KeyValue(m.t), Spec.CommaSuffix(m.suffix)); + } + wr + } + + opaque function Item(ghost arr: jarray, m: jitem, writer: Writer) : (wr: Writer) + requires m < arr + decreases arr, 0 + ensures wr.Bytes() == writer.Bytes() + Spec.Item(m) + { + var wr := Value(m.t, writer); + var wr := if m.suffix.Empty? then wr else StructuralView(m.suffix.t, wr); + assert wr.Bytes() == writer.Bytes() + (Spec.Value(m.t) + Spec.CommaSuffix(m.suffix)) by { + Seqs.LemmaConcatIsAssociative(writer.Bytes(), Spec.Value(m.t), Spec.CommaSuffix(m.suffix)); + } + wr + } +} \ No newline at end of file diff --git a/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/dfyconfig.toml b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/dfyconfig.toml index f5178c20475..5c14383b8fa 100644 --- a/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/dfyconfig.toml +++ b/Source/DafnyStandardLibraries/src/DafnyStdLibs/EnableNonLinearArithmetic/dfyconfig.toml @@ -13,6 +13,9 @@ excludes = [ track-print-effects = true enforce-determinism = true reads-clauses-on-methods = true +library = [ + "../../../binaries/DafnyStandardLibraries-arithmetic.doo" +] # Options important for sustainable development # of the libraries themselves. diff --git a/Source/DafnyStandardLibraries/src/DafnyStdLibs/TargetSpecific/Makefile b/Source/DafnyStandardLibraries/src/DafnyStdLibs/TargetSpecific/Makefile index cb0f35989f6..e8e4901ff29 100644 --- a/Source/DafnyStandardLibraries/src/DafnyStdLibs/TargetSpecific/Makefile +++ b/Source/DafnyStandardLibraries/src/DafnyStdLibs/TargetSpecific/Makefile @@ -13,6 +13,10 @@ DAFNY = dotnet run --project ../../../../Dafny --no-build -- DOO_FILE_SOURCE=../../../build/DafnyStandardLibraries-${TARGETLANG}.doo DOO_FILE_TARGET=../../../binaries/DafnyStandardLibraries-${TARGETLANG}.doo +verify: + $(DAFNY) verify ../../../src/DafnyStdLibs/EnableNonLinearArithmetic/dfyconfig.toml + $(DAFNY) verify ../../../src/DafnyStdLibs/DisableNonLinearArithmetic/dfyconfig.toml + build-binary: $(DAFNY) build -t:lib dfyconfig.toml \ `find . -name '*-${TARGETLANG}*.dfy'` \ From 46d9eb756ed3a40e0b67130deb16a6e6b9d4b8fb Mon Sep 17 00:00:00 2001 From: ET <57718207+turbotimon@users.noreply.github.com> Date: Wed, 6 Dec 2023 15:45:34 +0100 Subject: [PATCH 3/4] fix: No newline before "either" and "or" in Mac binary installation instruction (#4850) Co-authored-by: Fabio Madge --- docs/Installation.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/Installation.md b/docs/Installation.md index b78b677c53b..a52b2d47d4f 100644 --- a/docs/Installation.md +++ b/docs/Installation.md @@ -140,16 +140,18 @@ After the compiler dependencies are installed, you can run a quick test of the i ## Mac (Binary) {#Mac-binary} -To install a binary installation of dafny on Mac OS, do one of the following: -Either +To install a binary installation of Dafny on macOS, do one of the following: + +Either * Install the Mac binary version of Dafny, from `https://github.com/dafny-lang/dafny/releases/latest` - * Unzip the downloaded file in a (empty) location of your choice ($INSTALL) - * cd into the installation directory (`$INSTALL/dafny`) and run the script `./allow_on_mac.sh` - * dafny is run with the command `$INSTALL/dafny/dafny` + * Unzip the downloaded file in a (empty) location of your choice (`$INSTALL`) + * `cd` into the installation directory (`$INSTALL/dafny`) and run the script `./allow_on_mac.sh` + * Dafny is run with the command `$INSTALL/dafny/dafny` + or - * install dafny using brew, with the command `brew install dafny` (the version on brew sometimes lags the + * Install Dafny using brew, with the command `brew install dafny` (the version on brew sometimes lags the project release page) - * run dafny with the command `dafny` + * Run Dafny with the command `dafny` If you intend to use the Dafny compiler, install the appropriate tools as described [here](#compiling-dafny). From c90c6b4cfd35f5139f6b25465eb4132d129eb026 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Wed, 6 Dec 2023 17:44:38 +0100 Subject: [PATCH 4/4] Use `dafny:` Uris for standard library files (#4832) ### Description - For Dafny content that is embedded in the Dafny CLI, such as the standard library, expose their URIs using the `dafny:` scheme. This can be used by the LSP client to implement custom behavior for opening these files. ### How has this been tested? - Added the test StandardLibraryTest.GotoDefinition By submitting this pull request, I confirm that my contribution is made under the terms of the [MIT license](https://github.com/dafny-lang/dafny/blob/master/LICENSE.txt). --- Source/DafnyCore/DafnyFile.cs | 28 ++++++-- Source/DafnyCore/DafnyMain.cs | 15 +++- Source/DafnyCore/DafnyOptions.cs | 1 - Source/DafnyCore/DooFile.cs | 9 ++- Source/DafnyCore/Generic/Reporting.cs | 2 +- .../DafnyCore/Rewriters/PrecedenceLinter.cs | 9 +-- Source/DafnyDriver/Commands/DafnyCli.cs | 3 +- .../DafnyLanguageServer.Test.csproj | 1 + .../DafnyLanguageServerTestBase.cs | 3 +- .../Lookup/GoToDefinitionTest.cs | 18 ----- .../Util/ClientBasedLanguageServerTest.cs | 18 +++++ .../Various/StandardLibrary.cs | 37 ---------- .../Various/StandardLibraryTest.cs | 70 +++++++++++++++++++ .../Language/DafnyLangParser.cs | 15 +--- Source/DafnyLanguageServer/LanguageServer.cs | 1 - .../Workspace/Compilation.cs | 2 +- 16 files changed, 136 insertions(+), 96 deletions(-) delete mode 100644 Source/DafnyLanguageServer.Test/Various/StandardLibrary.cs create mode 100644 Source/DafnyLanguageServer.Test/Various/StandardLibraryTest.cs diff --git a/Source/DafnyCore/DafnyFile.cs b/Source/DafnyCore/DafnyFile.cs index 86fd840876e..a32a3f9235d 100644 --- a/Source/DafnyCore/DafnyFile.cs +++ b/Source/DafnyCore/DafnyFile.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; +using System.Linq; using System.Reflection; using System.Reflection.Metadata; using System.Reflection.PortableExecutable; @@ -16,13 +18,30 @@ public class DafnyFile { public string BaseName { get; private set; } public bool IsPreverified { get; set; } public bool IsPrecompiled { get; set; } - public bool IsPrerefined { get; private set; } public Func GetContent { get; set; } - public Uri Uri { get; } + public Uri Uri { get; private set; } [CanBeNull] public IToken Origin { get; } + private static readonly Dictionary ExternallyVisibleEmbeddedFiles = new(); + + public static Uri ExposeInternalUri(string externalName, Uri internalUri) { + var externalUri = new Uri("dafny:" + externalName); + ExternallyVisibleEmbeddedFiles[externalUri] = internalUri; + return externalUri; + } + public static DafnyFile CreateAndValidate(ErrorReporter reporter, IFileSystem fileSystem, DafnyOptions options, Uri uri, IToken origin) { + + var embeddedFile = ExternallyVisibleEmbeddedFiles.GetValueOrDefault(uri); + if (embeddedFile != null) { + var result = CreateAndValidate(reporter, fileSystem, options, embeddedFile, origin); + if (result != null) { + result.Uri = uri; + } + return result; + } + var filePath = uri.LocalPath; origin ??= Token.NoToken; @@ -32,7 +51,6 @@ public static DafnyFile CreateAndValidate(ErrorReporter reporter, IFileSystem fi Func getContent = null; bool isPreverified; bool isPrecompiled; - var isPrerefined = false; var extension = ".dfy"; if (uri.IsFile) { extension = Path.GetExtension(uri.LocalPath).ToLower(); @@ -98,7 +116,7 @@ public static DafnyFile CreateAndValidate(ErrorReporter reporter, IFileSystem fi dooFile = DooFile.Read(filePath); } - if (!dooFile.Validate(reporter, filePathForErrors, options, options.CurrentCommand, origin)) { + if (!dooFile.Validate(reporter, filePathForErrors, options, origin)) { return null; } @@ -109,7 +127,6 @@ public static DafnyFile CreateAndValidate(ErrorReporter reporter, IFileSystem fi // the DooFile class should encapsulate the serialization logic better // and expose a Program instead of the program text. getContent = () => new StringReader(dooFile.ProgramText); - isPrerefined = true; } else if (extension == ".dll") { isPreverified = true; // Technically only for C#, this is for backwards compatability @@ -127,7 +144,6 @@ public static DafnyFile CreateAndValidate(ErrorReporter reporter, IFileSystem fi return new DafnyFile(extension, canonicalPath, baseName, getContent, uri, origin) { IsPrecompiled = isPrecompiled, IsPreverified = isPreverified, - IsPrerefined = isPrerefined }; } diff --git a/Source/DafnyCore/DafnyMain.cs b/Source/DafnyCore/DafnyMain.cs index e29a5bbb703..6bf300b2d59 100644 --- a/Source/DafnyCore/DafnyMain.cs +++ b/Source/DafnyCore/DafnyMain.cs @@ -22,9 +22,18 @@ namespace Microsoft.Dafny { public class DafnyMain { - public static readonly string StandardLibrariesDooUriBase = "dllresource://DafnyPipeline/DafnyStandardLibraries"; - public static readonly Uri StandardLibrariesDooUri = new("dllresource://DafnyPipeline/DafnyStandardLibraries.doo"); - public static readonly Uri StandardLibrariesArithmeticDooUri = new("dllresource://DafnyPipeline/DafnyStandardLibraries-arithmetic.doo"); + public static readonly Dictionary standardLibrariesDooUriTarget = new(); + public static readonly Uri StandardLibrariesDooUri = DafnyFile.ExposeInternalUri("DafnyStandardLibraries.dfy", + new("dllresource://DafnyPipeline/DafnyStandardLibraries.doo")); + public static readonly Uri StandardLibrariesArithmeticDooUri = DafnyFile.ExposeInternalUri("DafnyStandardLibraries-arithmetic.dfy", + new("dllresource://DafnyPipeline/DafnyStandardLibraries-arithmetic.doo")); + + static DafnyMain() { + foreach (var target in new[] { "cs", "java", "go", "py", "js", "notarget" }) { + standardLibrariesDooUriTarget[target] = DafnyFile.ExposeInternalUri($"DafnyStandardLibraries-{target}.dfy", + new($"dllresource://DafnyPipeline/DafnyStandardLibraries-{target}.doo")); + } + } public static void MaybePrintProgram(Program program, string filename, bool afterResolver) { if (filename == null) { diff --git a/Source/DafnyCore/DafnyOptions.cs b/Source/DafnyCore/DafnyOptions.cs index e694a89d303..d132d0e1e37 100644 --- a/Source/DafnyCore/DafnyOptions.cs +++ b/Source/DafnyCore/DafnyOptions.cs @@ -42,7 +42,6 @@ public class DafnyOptions : Bpl.CommandLineOptions { public IList CliRootSourceUris = new List(); public DafnyProject DafnyProject { get; set; } - public Command CurrentCommand { get; set; } public static void ParseDefaultFunctionOpacity(Option option, Bpl.CommandLineParseState ps, DafnyOptions options) { if (ps.ConfirmArgumentCount(1)) { diff --git a/Source/DafnyCore/DooFile.cs b/Source/DafnyCore/DooFile.cs index 05859951a44..8c130e66103 100644 --- a/Source/DafnyCore/DooFile.cs +++ b/Source/DafnyCore/DooFile.cs @@ -116,11 +116,10 @@ public DooFile(Program dafnyProgram) { private DooFile() { } - public bool Validate(ErrorReporter reporter, string filePath, DafnyOptions options, Command currentCommand, - IToken origin) { - if (currentCommand == null) { + public bool Validate(ErrorReporter reporter, string filePath, DafnyOptions options, IToken origin) { + if (!options.UsingNewCli) { reporter.Error(MessageSource.Project, origin, - $"Cannot load {filePath}: .doo files cannot be used with the legacy CLI"); + $"cannot load {filePath}: .doo files cannot be used with the legacy CLI"); return false; } @@ -131,7 +130,7 @@ public bool Validate(ErrorReporter reporter, string filePath, DafnyOptions optio } var success = true; - var relevantOptions = currentCommand.Options.ToHashSet(); + var relevantOptions = options.Options.OptionArguments.Keys.ToHashSet(); foreach (var (option, check) in OptionChecks) { // It's important to only look at the options the current command uses, // because other options won't be initialized to the correct default value. diff --git a/Source/DafnyCore/Generic/Reporting.cs b/Source/DafnyCore/Generic/Reporting.cs index 858e4253a7e..65f16076d16 100644 --- a/Source/DafnyCore/Generic/Reporting.cs +++ b/Source/DafnyCore/Generic/Reporting.cs @@ -68,7 +68,7 @@ protected override bool MessageCore(MessageSource source, ErrorLevel level, stri Options.OutputWriter.Write(errorLine); - if (Options.Get(DafnyConsolePrinter.ShowSnippets) && tok != Token.NoToken) { + if (Options.Get(DafnyConsolePrinter.ShowSnippets) && tok.Uri != null) { TextWriter tw = new StringWriter(); new DafnyConsolePrinter(Options).WriteSourceCodeSnippet(tok.ToRange(), tw); Options.OutputWriter.Write(tw.ToString()); diff --git a/Source/DafnyCore/Rewriters/PrecedenceLinter.cs b/Source/DafnyCore/Rewriters/PrecedenceLinter.cs index d656b8e6a06..c9bd9201861 100644 --- a/Source/DafnyCore/Rewriters/PrecedenceLinter.cs +++ b/Source/DafnyCore/Rewriters/PrecedenceLinter.cs @@ -5,21 +5,18 @@ // SPDX-License-Identifier: MIT // //----------------------------------------------------------------------------- -using System.Collections.Generic; + using System.Linq; -using System; -using System.Diagnostics.Contracts; -using JetBrains.Annotations; using Microsoft.Boogie; using static Microsoft.Dafny.RewriterErrors; namespace Microsoft.Dafny { public class PrecedenceLinter : IRewriter { - private CompilationData compilation; + private readonly CompilationData compilation; // Don't perform linting on doo files in general, since the source has already been processed. internal override void PostResolve(ModuleDefinition moduleDefinition) { - if (moduleDefinition.tok.Uri != null && moduleDefinition.tok.Uri.LocalPath.EndsWith(".doo")) { + if (moduleDefinition.tok.Uri != null && !moduleDefinition.ShouldVerify(compilation)) { return; } foreach (var topLevelDecl in moduleDefinition.TopLevelDecls.OfType()) { diff --git a/Source/DafnyDriver/Commands/DafnyCli.cs b/Source/DafnyDriver/Commands/DafnyCli.cs index 893e429eb41..380479b4584 100644 --- a/Source/DafnyDriver/Commands/DafnyCli.cs +++ b/Source/DafnyDriver/Commands/DafnyCli.cs @@ -198,7 +198,6 @@ async Task Handle(InvocationContext context) { } } - dafnyOptions.CurrentCommand = command; dafnyOptions.ApplyDefaultOptionsWithoutSettingsDefault(); dafnyOptions.UsingNewCli = true; context.ExitCode = await continuation(dafnyOptions, context); @@ -490,7 +489,7 @@ public static ExitValue GetDafnyFiles(DafnyOptions options, var reporter = new ConsoleErrorReporter(options); if (options.CompilerName is null or "cs" or "java" or "go" or "py" or "js") { var targetName = options.CompilerName ?? "notarget"; - var stdlibDooUri = new Uri($"{DafnyMain.StandardLibrariesDooUriBase}-{targetName}.doo"); + var stdlibDooUri = DafnyMain.standardLibrariesDooUriTarget[targetName]; options.CliRootSourceUris.Add(stdlibDooUri); dafnyFiles.Add(DafnyFile.CreateAndValidate(reporter, OnDiskFileSystem.Instance, options, stdlibDooUri, Token.Cli)); } diff --git a/Source/DafnyLanguageServer.Test/DafnyLanguageServer.Test.csproj b/Source/DafnyLanguageServer.Test/DafnyLanguageServer.Test.csproj index 3893ea32733..37c2aa54049 100644 --- a/Source/DafnyLanguageServer.Test/DafnyLanguageServer.Test.csproj +++ b/Source/DafnyLanguageServer.Test/DafnyLanguageServer.Test.csproj @@ -27,6 +27,7 @@ + diff --git a/Source/DafnyLanguageServer.Test/DafnyLanguageServerTestBase.cs b/Source/DafnyLanguageServer.Test/DafnyLanguageServerTestBase.cs index 4404a095232..094695d5afb 100644 --- a/Source/DafnyLanguageServer.Test/DafnyLanguageServerTestBase.cs +++ b/Source/DafnyLanguageServer.Test/DafnyLanguageServerTestBase.cs @@ -106,7 +106,8 @@ private Action GetServerOptionsAction(Action { options.WithDafnyLanguageServer(() => { }); diff --git a/Source/DafnyLanguageServer.Test/Lookup/GoToDefinitionTest.cs b/Source/DafnyLanguageServer.Test/Lookup/GoToDefinitionTest.cs index fdf700402bf..ece396ec1c0 100644 --- a/Source/DafnyLanguageServer.Test/Lookup/GoToDefinitionTest.cs +++ b/Source/DafnyLanguageServer.Test/Lookup/GoToDefinitionTest.cs @@ -104,24 +104,6 @@ requires Err? await AssertPositionsLineUpWithRanges(source); } - ///

- /// Given with N positions, for each K from 0 to N exclusive, - /// assert that a RequestDefinition at position K - /// returns either the Kth range, or the range with key K (as a string). - /// - private async Task AssertPositionsLineUpWithRanges(string source) { - MarkupTestFile.GetPositionsAndNamedRanges(source, out var cleanSource, - out var positions, out var ranges); - - var documentItem = await CreateOpenAndWaitForResolve(cleanSource); - for (var index = 0; index < positions.Count; index++) { - var position = positions[index]; - var range = ranges.ContainsKey(string.Empty) ? ranges[string.Empty][index] : ranges[index.ToString()].Single(); - var result = (await RequestDefinition(documentItem, position)).Single(); - Assert.Equal(range, result.Location!.Range); - } - } - [Fact] public async Task StaticFunctionCall() { var source = @" diff --git a/Source/DafnyLanguageServer.Test/Util/ClientBasedLanguageServerTest.cs b/Source/DafnyLanguageServer.Test/Util/ClientBasedLanguageServerTest.cs index 1e7a492828a..7e3eec48c53 100644 --- a/Source/DafnyLanguageServer.Test/Util/ClientBasedLanguageServerTest.cs +++ b/Source/DafnyLanguageServer.Test/Util/ClientBasedLanguageServerTest.cs @@ -425,4 +425,22 @@ protected async Task GetDocumentItem(string source, string fil await client.OpenDocumentAndWaitAsync(documentItem, CancellationToken); return documentItem; } + + /// + /// Given with N positions, for each K from 0 to N exclusive, + /// assert that a RequestDefinition at position K + /// returns either the Kth range, or the range with key K (as a string). + /// + protected async Task AssertPositionsLineUpWithRanges(string source, string filePath = null) { + MarkupTestFile.GetPositionsAndNamedRanges(source, out var cleanSource, + out var positions, out var ranges); + + var documentItem = await CreateOpenAndWaitForResolve(cleanSource, filePath); + for (var index = 0; index < positions.Count; index++) { + var position = positions[index]; + var range = ranges.ContainsKey(string.Empty) ? ranges[string.Empty][index] : ranges[index.ToString()].Single(); + var result = (await RequestDefinition(documentItem, position)).Single(); + Assert.Equal(range, result.Location!.Range); + } + } } \ No newline at end of file diff --git a/Source/DafnyLanguageServer.Test/Various/StandardLibrary.cs b/Source/DafnyLanguageServer.Test/Various/StandardLibrary.cs deleted file mode 100644 index 9d5b144ae02..00000000000 --- a/Source/DafnyLanguageServer.Test/Various/StandardLibrary.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.IO; -using System.Threading.Tasks; -using Microsoft.Dafny.LanguageServer.IntegrationTest.Util; -using Microsoft.Extensions.Logging; -using OmniSharp.Extensions.LanguageServer.Protocol.Models; -using Xunit; -using Xunit.Abstractions; - -namespace Microsoft.Dafny.LanguageServer.IntegrationTest.Various; - -public class StandardLibrary : ClientBasedLanguageServerTest { - [Fact] - public async Task CanUseWrappers() { - var source = @" -import opened DafnyStdLibs.Wrappers - -method Foo() returns (s: Option) { - return Some(3); -}".TrimStart(); - - var projectSource = @" -[options] -standard-libraries = true"; - - var withoutStandardLibraries = CreateAndOpenTestDocument(source); - var diagnostics1 = await GetLastDiagnostics(withoutStandardLibraries); - Assert.Single(diagnostics1); - - var directory = Path.GetTempFileName(); - CreateAndOpenTestDocument(projectSource, Path.Combine(directory, DafnyProject.FileName)); - CreateAndOpenTestDocument(source, Path.Combine(directory, "document.dfy")); - await AssertNoDiagnosticsAreComing(CancellationToken); - } - - public StandardLibrary(ITestOutputHelper output, LogLevel dafnyLogLevel = LogLevel.Information) : base(output, dafnyLogLevel) { - } -} \ No newline at end of file diff --git a/Source/DafnyLanguageServer.Test/Various/StandardLibraryTest.cs b/Source/DafnyLanguageServer.Test/Various/StandardLibraryTest.cs new file mode 100644 index 00000000000..39bfc410a6e --- /dev/null +++ b/Source/DafnyLanguageServer.Test/Various/StandardLibraryTest.cs @@ -0,0 +1,70 @@ +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Dafny.LanguageServer.IntegrationTest.Util; +using Microsoft.Extensions.Logging; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.Dafny.LanguageServer.IntegrationTest.Various; + +public class StandardLibraryTest : ClientBasedLanguageServerTest { + [Fact] + public async Task CanUseWrappers() { + var source = @" +import opened DafnyStdLibs.Wrappers + +const triggerSemicolonWarning := 3; + +method Foo() returns (s: Option) { + return Some(3); +}".TrimStart(); + + var projectSource = @" +[options] +standard-libraries = true"; + + var withoutStandardLibraries = CreateAndOpenTestDocument(source); + var diagnostics1 = await GetLastDiagnostics(withoutStandardLibraries, DiagnosticSeverity.Error); + Assert.Single(diagnostics1); + + var directory = Path.GetTempFileName(); + CreateAndOpenTestDocument(projectSource, Path.Combine(directory, DafnyProject.FileName)); + var document = CreateAndOpenTestDocument(source, Path.Combine(directory, "document.dfy")); + var diagnostics2 = await GetLastDiagnostics(document); + Assert.Single(diagnostics2); + Assert.Equal(DiagnosticSeverity.Warning, diagnostics2[0].Severity); + } + + [Fact] + public async Task GotoDefinition() { + var source = @" +import opened DafnyStdLibs.Wrappers + +method Foo() returns (s: >) { + return Some(3); +}".TrimStart(); + + var projectSource = @" +[options] +standard-libraries = true"; + + var directory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + Directory.CreateDirectory(directory); + await File.WriteAllTextAsync(Path.Combine(directory, DafnyProject.FileName), projectSource); + + MarkupTestFile.GetPositionsAndNamedRanges(source, out var cleanSource, + out var positions, out var ranges); + + var filePath = Path.Combine(directory, "StandardLibraryGotoDefinition.dfy"); + var documentItem = CreateAndOpenTestDocument(cleanSource, filePath); + await AssertNoDiagnosticsAreComing(CancellationToken); + var result = await RequestDefinition(documentItem, positions[0]); + Assert.Equal(new Uri("dafny:DafnyStandardLibraries.dfy"), result.Single().Location.Uri); + } + + public StandardLibraryTest(ITestOutputHelper output, LogLevel dafnyLogLevel = LogLevel.Information) : base(output, dafnyLogLevel) { + } +} \ No newline at end of file diff --git a/Source/DafnyLanguageServer/Language/DafnyLangParser.cs b/Source/DafnyLanguageServer/Language/DafnyLangParser.cs index 106bafce449..5acafaea483 100644 --- a/Source/DafnyLanguageServer/Language/DafnyLangParser.cs +++ b/Source/DafnyLanguageServer/Language/DafnyLangParser.cs @@ -39,20 +39,7 @@ public Program Parse(Compilation compilation, CancellationToken cancellationToke var beforeParsing = DateTime.Now; try { - var rootFiles = compilation.RootFiles!; - List dafnyFiles = new(); - foreach (var rootFile in rootFiles) { - try { - dafnyFiles.Add(rootFile); - if (logger.IsEnabled(LogLevel.Trace)) { - logger.LogTrace($"Parsing file with uri {rootFile.Uri} and content\n{rootFile.GetContent().ReadToEnd()}"); - } - } catch (IOException) { - logger.LogError($"Tried to parse file {rootFile} that could not be found"); - } - } - - return programParser.ParseFiles(compilation.Project.ProjectName, dafnyFiles, compilation.Reporter, cancellationToken); + return programParser.ParseFiles(compilation.Project.ProjectName, compilation.RootFiles, compilation.Reporter, cancellationToken); } finally { telemetryPublisher.PublishTime("Parse", compilation.Project.Uri.ToString(), DateTime.Now - beforeParsing); diff --git a/Source/DafnyLanguageServer/LanguageServer.cs b/Source/DafnyLanguageServer/LanguageServer.cs index 73bbe302c99..ba700ecf2b5 100644 --- a/Source/DafnyLanguageServer/LanguageServer.cs +++ b/Source/DafnyLanguageServer/LanguageServer.cs @@ -47,7 +47,6 @@ related locations public static void ConfigureDafnyOptionsForServer(DafnyOptions dafnyOptions) { dafnyOptions.Set(DafnyConsolePrinter.ShowSnippets, true); - dafnyOptions.PrintIncludesMode = DafnyOptions.IncludesModes.None; // TODO This may be subject to change. See Microsoft.Boogie.Counterexample diff --git a/Source/DafnyLanguageServer/Workspace/Compilation.cs b/Source/DafnyLanguageServer/Workspace/Compilation.cs index c9688f7d1dc..43c9142db54 100644 --- a/Source/DafnyLanguageServer/Workspace/Compilation.cs +++ b/Source/DafnyLanguageServer/Workspace/Compilation.cs @@ -132,7 +132,7 @@ private IReadOnlyList DetermineRootFiles() { if (Options.Get(CommonOptionBag.UseStandardLibraries)) { if (Options.CompilerName is null or "cs" or "java" or "go" or "py" or "js") { var targetName = Options.CompilerName ?? "notarget"; - var stdlibDooUri = new Uri($"{DafnyMain.StandardLibrariesDooUriBase}-{targetName}.doo"); + var stdlibDooUri = DafnyMain.standardLibrariesDooUriTarget[targetName]; Options.CliRootSourceUris.Add(stdlibDooUri); result.Add(DafnyFile.CreateAndValidate(errorReporter, OnDiskFileSystem.Instance, Options, stdlibDooUri, Project.StartingToken)); }