Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JSON support #51

Merged
merged 92 commits into from
May 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
92 commits
Select commit Hold shift + click to select a range
4e43e63
feat: Add a JSON serializer/deserializer
cpitclaudel May 29, 2022
f611179
json: Add an efficient serializer
cpitclaudel May 29, 2022
e3d8e83
json: Move away from member functions to reduce the number of lambdas
cpitclaudel May 30, 2022
c5394f7
json: Clean up grammar and put separators after items, not before
cpitclaudel May 30, 2022
72708eb
json: Complete soundness proof
cpitclaudel May 31, 2022
2a0c713
json: Add a clean low-level API
cpitclaudel May 31, 2022
593b32e
json: Speed things up using `by method` bodies in a few places
cpitclaudel Jun 1, 2022
4c50ce6
json: Small cleanup in types
cpitclaudel Jun 1, 2022
0198334
json(wip): Start working on high-level API
cpitclaudel Aug 31, 2022
a827a77
json: Clean up encoder and add decoder
cpitclaudel Sep 1, 2022
8af0e54
json: Optimize traversals and add support for unicode escapes
cpitclaudel Sep 1, 2022
32068c0
json: Finish unicode encoding and decoding
cpitclaudel Sep 2, 2022
5ffcf35
json: Adopt a consistent naming scheme across modules
cpitclaudel Sep 2, 2022
6ba1735
json: Clean up API files
cpitclaudel Sep 2, 2022
74f9b0b
json: Merge latest changes to the vectors library
cpitclaudel Sep 2, 2022
a0a8166
json: Move IntPow and IntLog to their own utility module
cpitclaudel Sep 2, 2022
c032f8b
json: Add a README
cpitclaudel Sep 2, 2022
2de6306
json: Add a useful postcondition to Views.Merge
cpitclaudel Sep 2, 2022
92a8f8d
json: Speed up a proof
cpitclaudel Sep 2, 2022
390e288
json: Move all math code to NonlinearArithmetic/ and use /noNLArith
cpitclaudel Sep 2, 2022
dcd06cc
json: Add a tutorial
cpitclaudel Sep 2, 2022
f23ed82
Update src/JSON/README.md
cpitclaudel Sep 15, 2022
efc4b0b
json: Fix two typos in the README
cpitclaudel Sep 15, 2022
d7064d2
json: Add a TODO
cpitclaudel Dec 29, 2022
4e6b74a
Merge remote-tracking branch 'origin/master' into json-merge
cpitclaudel Dec 29, 2022
d75efc1
json: Rename parameters and functions based on review
cpitclaudel Dec 30, 2022
ddea84f
json: Update to latest Dafny
cpitclaudel Dec 30, 2022
92dcba1
json: Rename LowLevel and HighLevel to Concrete and Abstract
cpitclaudel Dec 30, 2022
515c6fb
json: More renamings
cpitclaudel Dec 30, 2022
95462d2
json: Apply @MikaelMayer's feedback
cpitclaudel Dec 30, 2022
1670c1d
json: Add lit headers to run verification and tests in CI
cpitclaudel Dec 30, 2022
b448fde
Merge branch 'master' into json-merge
alex-chew Feb 20, 2023
cdb9fd0
Merge branch 'master' into json-merge
robin-aws Mar 4, 2023
3c71738
Updating lit syntax and a few function methods
robin-aws Mar 4, 2023
d1bc8dc
Remaining lit syntax fixes
robin-aws Mar 4, 2023
634f88c
Updating CI to use 3.13.1 and 4.0.0
robin-aws Mar 4, 2023
08cf34d
Split up the most expensive assertion batch
robin-aws Mar 6, 2023
26af65f
Merge branch 'master' into json-merge
fabiomadge Mar 16, 2023
578cb95
drop support for old Dafny versions
fabiomadge Mar 16, 2023
b96bbae
format
fabiomadge Mar 16, 2023
eb41584
fix doctest
fabiomadge Mar 16, 2023
899fcb7
fix workflow
fabiomadge Mar 16, 2023
9a321aa
drop old Dafny in the doc tests as well
fabiomadge Mar 16, 2023
06dd2c6
stable formatting
fabiomadge Mar 16, 2023
22f2258
update `setup-dafny-action`
fabiomadge Mar 16, 2023
829ae34
update literals
fabiomadge Mar 16, 2023
8c7efb8
weaken test
fabiomadge Mar 16, 2023
9b4bd4a
revert assert by
fabiomadge Mar 16, 2023
7fb9e02
Dafny 4
fabiomadge Mar 20, 2023
a1cae95
Optimize UnicodeEncodingForm.EncodeScalarSequence
robin-aws Apr 6, 2023
5553fb8
fix Serializer
fabiomadge Apr 11, 2023
7723fb5
`RUN: %verify`
fabiomadge Apr 11, 2023
5d196cd
Merge branch 'json-merge' of github.com:dafny-lang/libraries into jso…
robin-aws Apr 19, 2023
e0827c5
fix Deserializer
fabiomadge Apr 19, 2023
942be81
Generalize JSON code to work with either `--unicode-char` mode
robin-aws Apr 19, 2023
32bddbe
Merge branch 'master' into json-merge
fabiomadge Apr 19, 2023
2029a96
format
fabiomadge Apr 19, 2023
c984b22
docs
fabiomadge Apr 19, 2023
bbf2891
Merge branch 'json-merge' of github.com:dafny-lang/libraries into jso…
robin-aws Apr 20, 2023
99d189b
Axiomatize trivially true but expensive assertion
robin-aws Apr 20, 2023
dc50674
Refactor - resolves but doesn’t verify yet
robin-aws Apr 20, 2023
0a8acf7
really fix the deserializer
fabiomadge Apr 21, 2023
d36231a
Attempt at function by method, doesn’t verify yet
robin-aws Apr 26, 2023
43e51b1
Revert "Attempt at function by method, doesn’t verify yet"
robin-aws Apr 26, 2023
989c6d9
Simple extra-argument tail recursion
robin-aws Apr 26, 2023
407432f
Have you even ever coded before Robin
robin-aws Apr 26, 2023
ff5ce0b
Bound resource count instead of time
robin-aws Apr 27, 2023
0ea991d
Bound resource count instead of time
robin-aws Apr 27, 2023
99f0662
Don’t include UnicodeStrings directly, augment CI to test both ways
robin-aws Apr 27, 2023
fb26d7f
Revert "Don’t include UnicodeStrings directly, augment CI to test bot…
robin-aws Apr 27, 2023
10545c8
Different approach with multiple lit commands instead
robin-aws Apr 27, 2023
e84b10a
Tweak comment
robin-aws May 3, 2023
53ce3dd
ZeroCopy code doesn’t depend on UnicodeStrings module(s)
robin-aws May 3, 2023
6136cda
Formatting
robin-aws May 3, 2023
4aacbd6
Fix README.md
robin-aws May 3, 2023
7d626c5
Actually check-examples uses —unicode-char:false
robin-aws May 3, 2023
c35669c
Rename JSON.AST to JSON.Values
robin-aws May 4, 2023
4287c0b
Add Bug note, test on Java too
robin-aws May 4, 2023
79551df
Merge branch 'json-merge-without-parameterized-subset-types' into jso…
robin-aws May 4, 2023
8992608
Remove unused function that can’t be compiled for Java
robin-aws May 4, 2023
f277ffe
Formatting
robin-aws May 4, 2023
d2fa10f
Double checking ZeroCopy/Deserializer.dfy verifies with the subset types
robin-aws May 4, 2023
acdb6ec
Revert "Double checking ZeroCopy/Deserializer.dfy verifies with the s…
robin-aws May 4, 2023
ceed951
Progress on verifying again
robin-aws May 4, 2023
fc2ee8c
Revert "Progress on verifying again"
robin-aws May 4, 2023
53b7abf
Undoing subset type workaround
robin-aws May 4, 2023
2753e76
Formatting
robin-aws May 4, 2023
35cca47
Apply suggestions from code review
robin-aws May 5, 2023
62d5c89
Removing stale TODOs
robin-aws May 5, 2023
da1b5ac
Mikael’s calc rewrite
robin-aws May 5, 2023
45055e1
Formatting
robin-aws May 5, 2023
a58e01d
I said, FORMATTING
robin-aws May 5, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/check-examples-in-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
strategy:
fail-fast: false
matrix:
version: [ 3.12.0, 3.13.1, 4.0.0 ]
version: [ 3.13.1, 4.0.0 ]
os: [ ubuntu-latest ]

runs-on: ${{ matrix.os }}
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/check-format.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ jobs:
- uses: actions/checkout@v3

- name: Install Dafny
uses: dafny-lang/setup-dafny-action@v1
uses: dafny-lang/setup-dafny-action@v1.6.1
with:
dafny-version: "nightly-2023-02-15-567a5ba"
dafny-version: "4.0.0"

- name: Install lit
run: pip install lit OutputCheck
Expand Down
7 changes: 1 addition & 6 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,7 @@ jobs:
verification:
strategy:
matrix:
# nightly-latest to catch anything that breaks these tests in current development
# 2/18/2023 version is the first that supports logging, but it is not supported by setup-dafny-action 1.6.1
# 3.11.0 supports new CLI but does not support logging
# setup-dafny-action does not yet support 3.13.1 or recent nightly-lates

version: [ nightly-latest, nightly-2023-02-18-ef4f346, 3.11.0, 3.12.0, 3.13.1, 4.0.0 ]
version: [ nightly-latest ]

uses: ./.github/workflows/reusable-tests.yml
with:
Expand Down
11 changes: 1 addition & 10 deletions .github/workflows/reusable-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,23 +39,14 @@ jobs:
- name: Set up JS dependencies
run: npm install bignumber.js

- name: Verify Code and Examples without logging
id: nolog
if: inputs.dafny-version == '3.11.0'
run: lit --time-tests -v .

- name: Verify Code and Examples
id: withlog
if: steps.nolog.conclusion == 'skipped'
run: |
lit --time-tests -v --param 'dafny_params=--log-format trx --log-format csv' .

- name: Generate Report
if: always() && steps.withlog.conclusion != 'skipped'
run: find . -name '*.csv' -print0 | xargs -0 --verbose dafny-reportgenerator summarize-csv-results --max-duration-seconds 10
run: find . -name '*.csv' -print0 | xargs -0 --verbose dafny-reportgenerator summarize-csv-results --max-resource-count 40000000

- uses: actions/upload-artifact@v2 # upload test results
if: always() && steps.withlog.conclusion != 'skipped'
with:
name: verification-results
path: '**/TestResults/*.trx'
Expand Down
6 changes: 1 addition & 5 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,7 @@ jobs:
verification:
strategy:
matrix:
# nightly-latest to catch anything that breaks these tests in current development
# 2/18/2023 version is the first that supports logging, but it is not supported by dafny-setup-action 1.6.1
# 3.11.0 supports new CLI but does not support logging
# setup-dafny-action does not yet support 3.13.1 or recent nightly-latest
version: [ 3.11.0, 3.12.0, 3.13.1, 4.0.0 ]
version: [ 3.13.1, 4.0.0 ]

uses: ./.github/workflows/reusable-tests.yml
with:
Expand Down
2 changes: 2 additions & 0 deletions lit.site.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ resolveArgs = ' resolve --use-basename-for-filename ' + standardArguments
verifyArgs = ' verify --use-basename-for-filename --cores:2 --verification-time-limit:300 ' + standardArguments
buildArgs = ' build --use-basename-for-filename --cores:2 --verification-time-limit:300 ' + standardArguments
runArgs = ' run --use-basename-for-filename --cores:2 --verification-time-limit:300 ' + standardArguments
testArgs = ' test --use-basename-for-filename --cores:2 --verification-time-limit:300 ' + standardArguments

config.substitutions.append( ('%trargs', '--use-basename-for-filename --cores:2 --verification-time-limit:300' ) )

Expand All @@ -148,6 +149,7 @@ config.substitutions.append( ('%verify', dafnyExecutable + verifyArgs ) )
config.substitutions.append( ('%translate', dafnyExecutable + ' translate' ) )
config.substitutions.append( ('%build', dafnyExecutable + buildArgs ) )
config.substitutions.append( ('%run', dafnyExecutable + runArgs ) )
config.substitutions.append( ('%test', dafnyExecutable + runArgs ) )

# config.substitutions.append( ('%repositoryRoot', repositoryRoot) )
# config.substitutions.append( ('%binaryDir', binaryDir) )
Expand Down
27 changes: 24 additions & 3 deletions src/BoundedInts.dfy
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

module {:options "-functionSyntax:4"} BoundedInts {
const TWO_TO_THE_0: int := 1

const TWO_TO_THE_1: int := 2
const TWO_TO_THE_2: int := 4
const TWO_TO_THE_4: int := 16
Expand All @@ -20,12 +19,12 @@ module {:options "-functionSyntax:4"} BoundedInts {
const TWO_TO_THE_512: int :=
0x1_00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000;

newtype uint8 = x: int | 0 <= x < TWO_TO_THE_8
newtype uint8 = x: int | 0 <= x < TWO_TO_THE_8
newtype uint16 = x: int | 0 <= x < TWO_TO_THE_16
newtype uint32 = x: int | 0 <= x < TWO_TO_THE_32
newtype uint64 = x: int | 0 <= x < TWO_TO_THE_64

newtype int8 = x: int | -0x80 <= x < 0x80
newtype int8 = x: int | -0x80 <= x < 0x80
newtype int16 = x: int | -0x8000 <= x < 0x8000
newtype int32 = x: int | -0x8000_0000 <= x < 0x8000_0000
newtype int64 = x: int | -0x8000_0000_0000_0000 <= x < 0x8000_0000_0000_0000
Expand All @@ -35,4 +34,26 @@ module {:options "-functionSyntax:4"} BoundedInts {
newtype nat32 = x: int | 0 <= x < 0x8000_0000
newtype nat64 = x: int | 0 <= x < 0x8000_0000_0000_0000

const UINT8_MAX: uint8 := 0xFF
MikaelMayer marked this conversation as resolved.
Show resolved Hide resolved
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There might be some redundancy with the PR with the Heap. Could you synchronize with @prvshah51 to ensure we define these constant in only one place?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Parva didn't include these in the end; right? So no conflict.


type byte = uint8
type bytes = seq<byte>
newtype opt_byte = c: int | -1 <= c < TWO_TO_THE_8
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wish we could automatically optimize Option<byte> to this in code generation instead, and the snake casing also feels wrong for a type definition in Dafny. I think I'd be happier with keeping this internal to the JSON code if possible.

}
47 changes: 45 additions & 2 deletions src/Collections/Sequences/Seq.dfy
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,26 @@ module {:options "-functionSyntax:4"} Seq {
*
***********************************************************/

/* If a predicate is true at every index of a sequence,
it is true for every member of the sequence as a collection.
Useful for converting quantifiers between the two forms
to satisfy a precondition in the latter form. */
lemma IndexingImpliesMembership<T>(p: T -> bool, xs: seq<T>)
requires forall i | 0 <= i < |xs| :: p(xs[i])
ensures forall t | t in xs :: p(t)
{
}

/* 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<T>(p: T -> bool, xs: seq<T>)
requires forall t | t in xs :: p(t)
ensures forall i | 0 <= i < |xs| :: p(xs[i])
{
}

/* Is true if the sequence xs is a prefix of the sequence ys. */
ghost predicate IsPrefix<T>(xs: seq<T>, ys: seq<T>)
ensures IsPrefix(xs, ys) ==> (|xs| <= |ys| && xs == ys[..|xs|])
Expand Down Expand Up @@ -603,8 +623,12 @@ module {:options "-functionSyntax:4"} Seq {
ensures forall i {:trigger result[i]} :: 0 <= i < |xs| ==> result[i] == f(xs[i]);
reads set i, o | 0 <= i < |xs| && o in f.reads(xs[i]) :: o
{
if |xs| == 0 then []
else [f(xs[0])] + Map(f, xs[1..])
// This uses a sequence comprehension because it will usually be
// more efficient when compiled, allocating the storage for |xs| elements
// once instead of creating a chain of |xs| single element concatenations.
seq(|xs|, i requires 0 <= i < |xs| && f.requires(xs[i])
reads set i,o | 0 <= i < |xs| && o in f.reads(xs[i]) :: o
=> f(xs[i]))
}

/* Applies a function to every element of a sequence, returning a Result value (which is a
Expand Down Expand Up @@ -803,6 +827,25 @@ module {:options "-functionSyntax:4"} Seq {
}
}

/* Optimized implementation of Flatten(Map(f, xs)). */
function FlatMap<T,R>(f: (T ~> seq<R>), xs: seq<T>): (result: seq<R>)
requires forall i :: 0 <= i < |xs| ==> f.requires(xs[i])
reads set i, o | 0 <= i < |xs| && o in f.reads(xs[i]) :: o
{
Flatten(Map(f, xs))
}
by method {
result := [];
ghost var unflattened: seq<seq<R>> := [];
for i := |xs| downto 0
invariant unflattened == Map(f, xs[i..])
invariant result == Flatten(unflattened)
{
var next := f(xs[i]);
unflattened := [next] + unflattened;
result := next + result;
}
}

/**********************************************************
*
Expand Down
40 changes: 40 additions & 0 deletions src/JSON/API.dfy
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// RUN: %verify "%s" --unicode-char:false ../Unicode/UnicodeStringsWithoutUnicodeChar.dfy
// RUN: %verify "%s" --unicode-char:true ../Unicode/UnicodeStringsWithUnicodeChar.dfy

include "Serializer.dfy"
include "Deserializer.dfy"
include "ZeroCopy/API.dfy"

module {:options "-functionSyntax:4"} JSON.API {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's think about names for a moment. Names are the crown of all the hard work you have been putting into this library, and now you have time to step back and think about it ^^.
Everything in library is an API, so using API as a name makes little sense. What about just JSON here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cannot use just JSON, unfortunately :( That's because definitions in the JSON module cannot use definitions in submodules of it, like JSON.XYZ.

I considered having two top-level modules JSON and JSONImpl, but that's not great either.

Finally, this respects the link between module names and file names (JSON.API in JSON/Api).

We ought to improve this in the new module system.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping JSON.API, it would be possible to create a file in the folder above JSON.dfy that refines this module, so that users don't need to write JSON.API? Could you do this? I don't see myself writing JSON.API to invoke JSON routines. JavaScript offers JSON.stringify and JSON.parse.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would introduce a mismatch between the file name and the file path. You can, however, do import JSON.API as JS, which is good, I think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is good, but not good enough for what we want to be a default library. I'll take a pass on that later.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed this is good enough given the whole repo reserves the right to rename things for now.

import opened BoundedInts
import opened Errors
import Values
import Serializer
import Deserializer
import ZeroCopy = ZeroCopy.API

function {:opaque} Serialize(js: Values.JSON) : (bs: SerializationResult<seq<byte>>)
{
var js :- Serializer.JSON(js);
ZeroCopy.Serialize(js)
}

method SerializeAlloc(js: Values.JSON) returns (bs: SerializationResult<array<byte>>)
{
var js :- Serializer.JSON(js);
bs := ZeroCopy.SerializeAlloc(js);
}

method SerializeInto(js: Values.JSON, bs: array<byte>) returns (len: SerializationResult<uint32>)
modifies bs
{
var js :- Serializer.JSON(js);
len := ZeroCopy.SerializeInto(js, bs);
}

function {:opaque} Deserialize(bs: seq<byte>) : (js: DeserializationResult<Values.JSON>)
{
var js :- ZeroCopy.Deserialize(bs);
Deserializer.JSON(js)
}
}
121 changes: 121 additions & 0 deletions src/JSON/ConcreteSyntax.Spec.dfy
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// RUN: %verify "%s"

include "Grammar.dfy"

module {:options "-functionSyntax:4"} JSON.ConcreteSyntax.Spec {
import opened BoundedInts

import Vs = Utils.Views.Core
import opened Grammar

function View(v: Vs.View) : bytes {
v.Bytes()
}

function Structural<T>(self: Structural<T>, fT: T -> bytes): bytes {
View(self.before) + fT(self.t) + View(self.after)
}

function StructuralView(self: Structural<Vs.View>): bytes {
Structural<Vs.View>(self, View)
}

function Maybe<T>(self: Maybe<T>, 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<T>(ts: seq<T>, fT: T --> bytes) : bytes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
function ConcatBytes<T>(ts: seq<T>, fT: T --> bytes) : bytes
// TODO: Generalize this to a Seq.MapConcat
function ConcatBytes<T>(ts: seq<T>, fT: T --> bytes) : bytes

requires forall d | d in ts :: fT.requires(d)
{
if |ts| == 0 then []
else fT(ts[0]) + ConcatBytes(ts[1..], fT)
}

function Bracketed<D, S>(self: Bracketed<Vs.View, D, S, Vs.View>, fDatum: Suffixed<D, S> --> 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<Structural<jcomma>>): bytes {
// BUG(https://github.com/dafny-lang/dafny/issues/2179)
Maybe<Structural<Vs.View>>(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) : bytes {
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)
}
}
Loading