Skip to content

Commit 3ff49f8

Browse files
cpitclaudelMikaelMayeralex-chewrobin-awsfabiomadge
authored
JSON support (#51)
Co-authored-by: Mikaël Mayer <[email protected]> Co-authored-by: Alex Chew <[email protected]> Co-authored-by: Robin Salkeld <[email protected]> Co-authored-by: Fabio Madge <[email protected]>
1 parent 7c386fa commit 3ff49f8

40 files changed

+4213
-29
lines changed

.github/workflows/check-examples-in-docs.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
strategy:
1616
fail-fast: false
1717
matrix:
18-
version: [ 3.12.0, 3.13.1, 4.0.0 ]
18+
version: [ 3.13.1, 4.0.0 ]
1919
os: [ ubuntu-latest ]
2020

2121
runs-on: ${{ matrix.os }}

.github/workflows/check-format.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ jobs:
1313
- uses: actions/checkout@v3
1414

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

2020
- name: Install lit
2121
run: pip install lit OutputCheck

.github/workflows/nightly.yml

+1-6
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,7 @@ jobs:
1414
verification:
1515
strategy:
1616
matrix:
17-
# nightly-latest to catch anything that breaks these tests in current development
18-
# 2/18/2023 version is the first that supports logging, but it is not supported by setup-dafny-action 1.6.1
19-
# 3.11.0 supports new CLI but does not support logging
20-
# setup-dafny-action does not yet support 3.13.1 or recent nightly-lates
21-
22-
version: [ nightly-latest, nightly-2023-02-18-ef4f346, 3.11.0, 3.12.0, 3.13.1, 4.0.0 ]
17+
version: [ nightly-latest ]
2318

2419
uses: ./.github/workflows/reusable-tests.yml
2520
with:

.github/workflows/reusable-tests.yml

+1-10
Original file line numberDiff line numberDiff line change
@@ -39,23 +39,14 @@ jobs:
3939
- name: Set up JS dependencies
4040
run: npm install bignumber.js
4141

42-
- name: Verify Code and Examples without logging
43-
id: nolog
44-
if: inputs.dafny-version == '3.11.0'
45-
run: lit --time-tests -v .
46-
4742
- name: Verify Code and Examples
48-
id: withlog
49-
if: steps.nolog.conclusion == 'skipped'
5043
run: |
5144
lit --time-tests -v --param 'dafny_params=--log-format trx --log-format csv' .
5245
5346
- name: Generate Report
54-
if: always() && steps.withlog.conclusion != 'skipped'
55-
run: find . -name '*.csv' -print0 | xargs -0 --verbose dafny-reportgenerator summarize-csv-results --max-duration-seconds 10
47+
run: find . -name '*.csv' -print0 | xargs -0 --verbose dafny-reportgenerator summarize-csv-results --max-resource-count 40000000
5648

5749
- uses: actions/upload-artifact@v2 # upload test results
58-
if: always() && steps.withlog.conclusion != 'skipped'
5950
with:
6051
name: verification-results
6152
path: '**/TestResults/*.trx'

.github/workflows/tests.yml

+1-5
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,7 @@ jobs:
1313
verification:
1414
strategy:
1515
matrix:
16-
# nightly-latest to catch anything that breaks these tests in current development
17-
# 2/18/2023 version is the first that supports logging, but it is not supported by dafny-setup-action 1.6.1
18-
# 3.11.0 supports new CLI but does not support logging
19-
# setup-dafny-action does not yet support 3.13.1 or recent nightly-latest
20-
version: [ 3.11.0, 3.12.0, 3.13.1, 4.0.0 ]
16+
version: [ 3.13.1, 4.0.0 ]
2117

2218
uses: ./.github/workflows/reusable-tests.yml
2319
with:

lit.site.cfg

+2
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ resolveArgs = ' resolve --use-basename-for-filename ' + standardArguments
140140
verifyArgs = ' verify --use-basename-for-filename --cores:2 --verification-time-limit:300 ' + standardArguments
141141
buildArgs = ' build --use-basename-for-filename --cores:2 --verification-time-limit:300 ' + standardArguments
142142
runArgs = ' run --use-basename-for-filename --cores:2 --verification-time-limit:300 ' + standardArguments
143+
testArgs = ' test --use-basename-for-filename --cores:2 --verification-time-limit:300 ' + standardArguments
143144

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

@@ -148,6 +149,7 @@ config.substitutions.append( ('%verify', dafnyExecutable + verifyArgs ) )
148149
config.substitutions.append( ('%translate', dafnyExecutable + ' translate' ) )
149150
config.substitutions.append( ('%build', dafnyExecutable + buildArgs ) )
150151
config.substitutions.append( ('%run', dafnyExecutable + runArgs ) )
152+
config.substitutions.append( ('%test', dafnyExecutable + runArgs ) )
151153

152154
# config.substitutions.append( ('%repositoryRoot', repositoryRoot) )
153155
# config.substitutions.append( ('%binaryDir', binaryDir) )

src/BoundedInts.dfy

+24-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
module {:options "-functionSyntax:4"} BoundedInts {
44
const TWO_TO_THE_0: int := 1
5-
65
const TWO_TO_THE_1: int := 2
76
const TWO_TO_THE_2: int := 4
87
const TWO_TO_THE_4: int := 16
@@ -20,12 +19,12 @@ module {:options "-functionSyntax:4"} BoundedInts {
2019
const TWO_TO_THE_512: int :=
2120
0x1_00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000;
2221

23-
newtype uint8 = x: int | 0 <= x < TWO_TO_THE_8
22+
newtype uint8 = x: int | 0 <= x < TWO_TO_THE_8
2423
newtype uint16 = x: int | 0 <= x < TWO_TO_THE_16
2524
newtype uint32 = x: int | 0 <= x < TWO_TO_THE_32
2625
newtype uint64 = x: int | 0 <= x < TWO_TO_THE_64
2726

28-
newtype int8 = x: int | -0x80 <= x < 0x80
27+
newtype int8 = x: int | -0x80 <= x < 0x80
2928
newtype int16 = x: int | -0x8000 <= x < 0x8000
3029
newtype int32 = x: int | -0x8000_0000 <= x < 0x8000_0000
3130
newtype int64 = x: int | -0x8000_0000_0000_0000 <= x < 0x8000_0000_0000_0000
@@ -35,4 +34,26 @@ module {:options "-functionSyntax:4"} BoundedInts {
3534
newtype nat32 = x: int | 0 <= x < 0x8000_0000
3635
newtype nat64 = x: int | 0 <= x < 0x8000_0000_0000_0000
3736

37+
const UINT8_MAX: uint8 := 0xFF
38+
const UINT16_MAX: uint16 := 0xFFFF
39+
const UINT32_MAX: uint32 := 0xFFFF_FFFF
40+
const UINT64_MAX: uint64 := 0xFFFF_FFFF_FFFF_FFFF
41+
42+
const INT8_MIN: int8 := -0x80
43+
const INT8_MAX: int8 := 0x7F
44+
const INT16_MIN: int16 := -0x8000
45+
const INT16_MAX: int16 := 0x7FFF
46+
const INT32_MIN: int32 := -0x8000_0000
47+
const INT32_MAX: int32 := 0x7FFFFFFF
48+
const INT64_MIN: int64 := -0x8000_0000_0000_0000
49+
const INT64_MAX: int64 := 0x7FFFFFFF_FFFFFFFF
50+
51+
const NAT8_MAX: nat8 := 0x7F
52+
const NAT16_MAX: nat16 := 0x7FFF
53+
const NAT32_MAX: nat32 := 0x7FFFFFFF
54+
const NAT64_MAX: nat64 := 0x7FFFFFFF_FFFFFFFF
55+
56+
type byte = uint8
57+
type bytes = seq<byte>
58+
newtype opt_byte = c: int | -1 <= c < TWO_TO_THE_8
3859
}

src/Collections/Sequences/Seq.dfy

+45-2
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,26 @@ module {:options "-functionSyntax:4"} Seq {
9090
*
9191
***********************************************************/
9292

93+
/* If a predicate is true at every index of a sequence,
94+
it is true for every member of the sequence as a collection.
95+
Useful for converting quantifiers between the two forms
96+
to satisfy a precondition in the latter form. */
97+
lemma IndexingImpliesMembership<T>(p: T -> bool, xs: seq<T>)
98+
requires forall i | 0 <= i < |xs| :: p(xs[i])
99+
ensures forall t | t in xs :: p(t)
100+
{
101+
}
102+
103+
/* If a predicate is true for every member of a sequence as a collection,
104+
it is true at every index of the sequence.
105+
Useful for converting quantifiers between the two forms
106+
to satisfy a precondition in the latter form. */
107+
lemma MembershipImpliesIndexing<T>(p: T -> bool, xs: seq<T>)
108+
requires forall t | t in xs :: p(t)
109+
ensures forall i | 0 <= i < |xs| :: p(xs[i])
110+
{
111+
}
112+
93113
/* Is true if the sequence xs is a prefix of the sequence ys. */
94114
ghost predicate IsPrefix<T>(xs: seq<T>, ys: seq<T>)
95115
ensures IsPrefix(xs, ys) ==> (|xs| <= |ys| && xs == ys[..|xs|])
@@ -603,8 +623,12 @@ module {:options "-functionSyntax:4"} Seq {
603623
ensures forall i {:trigger result[i]} :: 0 <= i < |xs| ==> result[i] == f(xs[i]);
604624
reads set i, o | 0 <= i < |xs| && o in f.reads(xs[i]) :: o
605625
{
606-
if |xs| == 0 then []
607-
else [f(xs[0])] + Map(f, xs[1..])
626+
// This uses a sequence comprehension because it will usually be
627+
// more efficient when compiled, allocating the storage for |xs| elements
628+
// once instead of creating a chain of |xs| single element concatenations.
629+
seq(|xs|, i requires 0 <= i < |xs| && f.requires(xs[i])
630+
reads set i,o | 0 <= i < |xs| && o in f.reads(xs[i]) :: o
631+
=> f(xs[i]))
608632
}
609633

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

830+
/* Optimized implementation of Flatten(Map(f, xs)). */
831+
function FlatMap<T,R>(f: (T ~> seq<R>), xs: seq<T>): (result: seq<R>)
832+
requires forall i :: 0 <= i < |xs| ==> f.requires(xs[i])
833+
reads set i, o | 0 <= i < |xs| && o in f.reads(xs[i]) :: o
834+
{
835+
Flatten(Map(f, xs))
836+
}
837+
by method {
838+
result := [];
839+
ghost var unflattened: seq<seq<R>> := [];
840+
for i := |xs| downto 0
841+
invariant unflattened == Map(f, xs[i..])
842+
invariant result == Flatten(unflattened)
843+
{
844+
var next := f(xs[i]);
845+
unflattened := [next] + unflattened;
846+
result := next + result;
847+
}
848+
}
806849

807850
/**********************************************************
808851
*

src/JSON/API.dfy

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// RUN: %verify "%s" --unicode-char:false ../Unicode/UnicodeStringsWithoutUnicodeChar.dfy
2+
// RUN: %verify "%s" --unicode-char:true ../Unicode/UnicodeStringsWithUnicodeChar.dfy
3+
4+
include "Serializer.dfy"
5+
include "Deserializer.dfy"
6+
include "ZeroCopy/API.dfy"
7+
8+
module {:options "-functionSyntax:4"} JSON.API {
9+
import opened BoundedInts
10+
import opened Errors
11+
import Values
12+
import Serializer
13+
import Deserializer
14+
import ZeroCopy = ZeroCopy.API
15+
16+
function {:opaque} Serialize(js: Values.JSON) : (bs: SerializationResult<seq<byte>>)
17+
{
18+
var js :- Serializer.JSON(js);
19+
ZeroCopy.Serialize(js)
20+
}
21+
22+
method SerializeAlloc(js: Values.JSON) returns (bs: SerializationResult<array<byte>>)
23+
{
24+
var js :- Serializer.JSON(js);
25+
bs := ZeroCopy.SerializeAlloc(js);
26+
}
27+
28+
method SerializeInto(js: Values.JSON, bs: array<byte>) returns (len: SerializationResult<uint32>)
29+
modifies bs
30+
{
31+
var js :- Serializer.JSON(js);
32+
len := ZeroCopy.SerializeInto(js, bs);
33+
}
34+
35+
function {:opaque} Deserialize(bs: seq<byte>) : (js: DeserializationResult<Values.JSON>)
36+
{
37+
var js :- ZeroCopy.Deserialize(bs);
38+
Deserializer.JSON(js)
39+
}
40+
}

src/JSON/ConcreteSyntax.Spec.dfy

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// RUN: %verify "%s"
2+
3+
include "Grammar.dfy"
4+
5+
module {:options "-functionSyntax:4"} JSON.ConcreteSyntax.Spec {
6+
import opened BoundedInts
7+
8+
import Vs = Utils.Views.Core
9+
import opened Grammar
10+
11+
function View(v: Vs.View) : bytes {
12+
v.Bytes()
13+
}
14+
15+
function Structural<T>(self: Structural<T>, fT: T -> bytes): bytes {
16+
View(self.before) + fT(self.t) + View(self.after)
17+
}
18+
19+
function StructuralView(self: Structural<Vs.View>): bytes {
20+
Structural<Vs.View>(self, View)
21+
}
22+
23+
function Maybe<T>(self: Maybe<T>, fT: T -> bytes): (bs: bytes)
24+
ensures self.Empty? ==> bs == []
25+
ensures self.NonEmpty? ==> bs == fT(self.t)
26+
{
27+
if self.Empty? then [] else fT(self.t)
28+
}
29+
30+
function ConcatBytes<T>(ts: seq<T>, fT: T --> bytes) : bytes
31+
requires forall d | d in ts :: fT.requires(d)
32+
{
33+
if |ts| == 0 then []
34+
else fT(ts[0]) + ConcatBytes(ts[1..], fT)
35+
}
36+
37+
function Bracketed<D, S>(self: Bracketed<Vs.View, D, S, Vs.View>, fDatum: Suffixed<D, S> --> bytes): bytes
38+
requires forall d | d < self :: fDatum.requires(d)
39+
{
40+
StructuralView(self.l) +
41+
ConcatBytes(self.data, fDatum) +
42+
StructuralView(self.r)
43+
}
44+
45+
function KeyValue(self: jKeyValue): bytes {
46+
String(self.k) + StructuralView(self.colon) + Value(self.v)
47+
}
48+
49+
function Frac(self: jfrac): bytes {
50+
View(self.period) + View(self.num)
51+
}
52+
53+
function Exp(self: jexp): bytes {
54+
View(self.e) + View(self.sign) + View(self.num)
55+
}
56+
57+
function Number(self: jnumber): bytes {
58+
View(self.minus) + View(self.num) + Maybe(self.frac, Frac) + Maybe(self.exp, Exp)
59+
}
60+
61+
function String(self: jstring): bytes {
62+
View(self.lq) + View(self.contents) + View(self.rq)
63+
}
64+
65+
function CommaSuffix(c: Maybe<Structural<jcomma>>): bytes {
66+
// BUG(https://github.com/dafny-lang/dafny/issues/2179)
67+
Maybe<Structural<Vs.View>>(c, StructuralView)
68+
}
69+
70+
function Member(self: jmember) : bytes {
71+
KeyValue(self.t) + CommaSuffix(self.suffix)
72+
}
73+
74+
function Item(self: jitem) : bytes {
75+
Value(self.t) + CommaSuffix(self.suffix)
76+
}
77+
78+
function Object(obj: jobject) : bytes {
79+
Bracketed(obj, (d: jmember) requires d < obj => Member(d))
80+
}
81+
82+
function Array(arr: jarray) : bytes {
83+
Bracketed(arr, (d: jitem) requires d < arr => Item(d))
84+
}
85+
86+
function Value(self: Value) : bytes {
87+
match self {
88+
case Null(n) => View(n)
89+
case Bool(b) => View(b)
90+
case String(str) => String(str)
91+
case Number(num) => Number(num)
92+
case Object(obj) => Object(obj)
93+
case Array(arr) => Array(arr)
94+
}
95+
}
96+
97+
lemma UnfoldValueNumber(v: Value)
98+
requires v.Number?
99+
ensures Value(v) == Number(v.num)
100+
{
101+
assert Value(v) == match v { case Number(num) => Number(num) case _ => []};
102+
}
103+
104+
lemma UnfoldValueObject(v: Value)
105+
requires v.Object?
106+
ensures Value(v) == Object(v.obj)
107+
{
108+
assert Value(v) == match v { case Object(obj) => Object(obj) case _ => []};
109+
}
110+
111+
lemma UnfoldValueArray(v: Value)
112+
requires v.Array?
113+
ensures Value(v) == Array(v.arr)
114+
{
115+
assert Value(v) == match v { case Array(arr) => Array(arr) case _ => []};
116+
}
117+
118+
function JSON(js: JSON) : bytes {
119+
Structural(js, Value)
120+
}
121+
}

0 commit comments

Comments
 (0)