// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 include "../../libraries/src/Wrappers.dfy" include "UInt.dfy" module StandardLibrary { import opened Wrappers import opened U = UInt lemma SeqTakeAppend(s: seq, i: int) requires 0 <= i < |s| ensures s[..i] + [s[i]] == s[..i + 1] {} function method {:tailrecursion} Join(ss: seq>, joiner: seq): (s: seq) requires 0 < |ss| { if |ss| == 1 then ss[0] else ss[0] + joiner + Join(ss[1..], joiner) } function method {:tailrecursion} Split(s: seq, delim: T): (res: seq>) ensures delim !in s ==> res == [s] ensures s == [] ==> res == [[]] ensures 0 < |res| ensures forall i :: 0 <= i < |res| ==> delim !in res[i] ensures Join(res, [delim]) == s decreases |s| { var i := FindIndexMatching(s, delim, 0); if i.Some? then [s[..i.value]] + Split(s[(i.value + 1)..], delim) else [s] } // split on first occurrence of delim, which must exist function method {:tailrecursion} SplitOnce(s: seq, delim: T): (res : (seq,seq)) requires delim in s ensures res.0 + [delim] + res.1 == s ensures !(delim in res.0) { var i := FindIndexMatching(s, delim, 0); assert i.Some?; (s[..i.value], s[(i.value + 1)..]) } // split on first occurrence of delim, return None if delim not present function method {:tailrecursion} SplitOnce?(s: seq, delim: T): (res : Option<(seq,seq)>) ensures res.Some? ==> res.value.0 + [delim] + res.value.1 == s ensures res.None? ==> !(delim in s) ensures res.Some? ==> !(delim in res.value.0) { var i :- FindIndexMatching(s, delim, 0); Some((s[..i], s[(i + 1)..])) } lemma WillSplitOnDelim(s: seq, delim: T, prefix: seq) requires |prefix| < |s| requires forall i :: 0 <= i < |prefix| ==> prefix[i] == s[i] requires delim !in prefix && s[|prefix|] == delim ensures Split(s, delim) == [prefix] + Split(s[|prefix| + 1..], delim) { calc { Split(s, delim); == var i := FindIndexMatching(s, delim, 0); if i.Some? then [s[..i.value]] + Split(s[i.value + 1..], delim) else [s]; == { FindIndexMatchingLocatesElem(s, delim, 0, |prefix|); assert FindIndexMatching(s, delim, 0).Some?; } [s[..|prefix|]] + Split(s[|prefix| + 1..], delim); == { assert s[..|prefix|] == prefix; } [prefix] + Split(s[|prefix| + 1..], delim); } } lemma WillNotSplitWithOutDelim(s: seq, delim: T) requires delim !in s ensures Split(s, delim) == [s] { calc { Split(s, delim); == var i := FindIndexMatching(s, delim, 0); if i.Some? then [s[..i.value]] + Split(s[i.value+1..], delim) else [s]; == { FindIndexMatchingLocatesElem(s, delim, 0, |s|); } [s]; } } lemma FindIndexMatchingLocatesElem(s: seq, c: T, start: nat, elemIndex: nat) requires start <= elemIndex <= |s| requires forall i :: start <= i < elemIndex ==> s[i] != c requires elemIndex == |s| || s[elemIndex] == c ensures FindIndexMatching(s, c, start) == if elemIndex == |s| then None else Some(elemIndex) decreases elemIndex - start {} function method FindIndexMatching(s: seq, c: T, i: nat): (index: Option) requires i <= |s| ensures index.Some? ==> i <= index.value < |s| && s[index.value] == c && c !in s[i..index.value] ensures index.None? ==> c !in s[i..] decreases |s| - i { FindIndex(s, x => x == c, i) } function method {:tailrecursion} FindIndex(s: seq, f: T -> bool, i: nat): (index: Option) requires i <= |s| ensures index.Some? ==> i <= index.value < |s| && f(s[index.value]) && (forall j :: i <= j < index.value ==> !f(s[j])) ensures index.None? ==> forall j :: i <= j < |s| ==> !f(s[j]) decreases |s| - i { if i == |s| then None else if f(s[i]) then Some(i) else FindIndex(s, f, i + 1) } function method {:tailrecursion} Filter(s: seq, f: T -> bool): (res: seq) ensures forall i :: 0 <= i < |s| && f(s[i]) ==> s[i] in res ensures forall i :: 0 <= i < |res| ==> res[i] in s && f(res[i]) ensures |res| <= |s| { if |s| == 0 then [] else if f(s[0]) then [s[0]] + Filter(s[1..], f) else Filter(s[1..], f) } lemma FilterIsDistributive(s: seq, s': seq, f: T -> bool) ensures Filter(s + s', f) == Filter(s, f) + Filter(s', f) { if s == [] { assert s + s' == s'; } else { var S := s + s'; var s1 := s[1..]; calc { Filter(S, f); == // def. Filter if f(S[0]) then [S[0]] + Filter(S[1..], f) else Filter(S[1..], f); == { assert S[0] == s[0] && S[1..] == s1 + s'; } if f(s[0]) then [s[0]] + Filter(s1 + s', f) else Filter(s1 + s', f); == { FilterIsDistributive(s1, s', f); } if f(s[0]) then [s[0]] + (Filter(s1, f) + Filter(s', f)) else Filter(s1, f) + Filter(s', f); == // associativity of + if f(s[0]) then ([s[0]] + Filter(s1, f)) + Filter(s', f) else Filter(s1, f) + Filter(s', f); == // distribute + over if-then-else (if f(s[0]) then [s[0]] + Filter(s1, f) else Filter(s1, f)) + Filter(s', f); == // def. Filter Filter(s, f) + Filter(s', f); } } } function method Min(a: int, b: int): int { if a < b then a else b } function method Fill(value: T, n: nat): seq ensures |Fill(value, n)| == n ensures forall i :: 0 <= i < n ==> Fill(value, n)[i] == value { seq(n, _ => value) } method SeqToArray(s: seq) returns (a: array) // "Fresh" expressions require editing memory ensures fresh(a) ensures a.Length == |s| ensures forall i :: 0 <= i < |s| ==> a[i] == s[i] { a := new T[|s|](i requires 0 <= i < |s| => s[i]); } lemma SeqPartsMakeWhole(s: seq, i: nat) requires 0 <= i <= |s| ensures s[..i] + s[i..] == s {} /* * Lexicographic comparison of sequences. * * Given two sequences `a` and `b` and a strict (that is, irreflexive) * ordering `less` on the elements of these sequences, determine whether or not * `a` is lexicographically "less than or equal to" `b`. * * `a` is lexicographically "less than or equal to" `b` holds iff * there exists a `k` such that * - the first `k` elements of `a` and `b` are the same * - either: * -- `a` has length `k` (that is, `a` is a prefix of `b`) * -- `a[k]` is strictly less (using `less`) than `b[k]` */ predicate method LexicographicLessOrEqual(a: seq, b: seq, less: (T, T) -> bool) { exists k :: 0 <= k <= |a| && LexicographicLessOrEqualAux(a, b, less, k) } predicate method LexicographicLessOrEqualAux(a: seq, b: seq, less: (T, T) -> bool, lengthOfCommonPrefix: nat) requires 0 <= lengthOfCommonPrefix <= |a| { lengthOfCommonPrefix <= |b| && (forall i :: 0 <= i < lengthOfCommonPrefix ==> a[i] == b[i]) && (lengthOfCommonPrefix == |a| || (lengthOfCommonPrefix < |b| && less(a[lengthOfCommonPrefix], b[lengthOfCommonPrefix]))) } /* * In order for the lexicographic ordering above to have the expected properties, the * relation "less" must be trichotomous and transitive. * * For an ordering `less` to be _trichotomous_ means that for any two `x` and `y`, * EXACTLY one of the following three conditions holds: * - less(x, y) * - x == y * - less(y, x) * Note that being trichotomous implies being irreflexive. */ predicate Trichotomous(less: (T, T) -> bool) { (forall x, y :: less(x, y) || x == y || less(y, x)) && // at least one of the three (forall x, y :: less(x, y) && less(y, x) ==> false) && // not both of the less's (forall x, y :: less(x, y) ==> x != y) // not a less and the equality } predicate Transitive(R: (T, T) -> bool) { forall x, y, z :: R(x, y) && R(y, z) ==> R(x, z) } /* * Here is an example relation and a lemma that says the relation is appropriate for use in * lexicographic orderings. */ lemma UInt8LessIsTrichotomousTransitive() ensures Trichotomous(UInt8Less) ensures Transitive(UInt8Less) { } /* * As the following lemmas show, the lexicographic ordering is reflexive, antisymmetric, transitive, and total. * The proofs are a bit pedantic and include steps that can be automated. */ lemma LexIsReflexive(a: seq, less: (T, T) -> bool) ensures LexicographicLessOrEqual(a, a, less) { assert LexicographicLessOrEqualAux(a, a, less, |a|); } lemma LexIsAntisymmetric(a: seq, b: seq, less: (T, T) -> bool) requires Trich: Trichotomous(less) requires LexicographicLessOrEqual(a, b, less) requires LexicographicLessOrEqual(b, a, less) ensures a == b { assert LessIrreflexive: forall x,y :: less(x, y) ==> x != y by { reveal Trich; } assert ASymmetric: forall x,y :: less(x, y) && less(y, x) ==> false by { reveal Trich; } var k0 :| 0 <= k0 <= |a| && LexicographicLessOrEqualAux(a, b, less, k0); var k1 :| 0 <= k1 <= |b| && LexicographicLessOrEqualAux(b, a, less, k1); var max := if k0 < k1 then k1 else k0; assert max <= |a| && max <= |b|; assert SameUntilMax: forall i :: 0 <= i < max ==> a[i] == b[i]; assert AA: k0 == |a| || (k0 < |b| && less(a[k0], b[k0])); assert BB: k1 == |b| || (k1 < |a| && less(b[k1], a[k1])); calc { true; ==> { reveal AA, BB; } (k0 == |a| || (k0 < |b| && less(a[k0], b[k0]))) && (k1 == |b| || (k1 < |a| && less(b[k1], a[k1]))); == // distribute && and || (k0 == |a| && k1 == |b|) || (k0 == |a| && k1 < |a| && less(b[k1], a[k1])) || (k0 < |b| && less(a[k0], b[k0]) && k1 == |b|) || (k0 < |b| && less(a[k0], b[k0]) && k1 < |a| && less(b[k1], a[k1])); == { reveal LessIrreflexive, SameUntilMax; } (k0 == |a| && k1 == |b|) || (k0 < |b| && less(a[k0], b[k0]) && k1 < |a| && less(b[k1], a[k1])); ==> { reveal LessIrreflexive, SameUntilMax; assert max <= k0 && max <= k1; } (k0 == |a| && k1 == |b|) || (k0 < |b| && less(a[k0], b[k0]) && k1 < |a| && less(b[k1], a[k1]) && k0 == k1 == max); == { reveal ASymmetric; } k0 == |a| && k1 == |b|; ==> { assert |a| == k0 <= max && |b| == k1 <= max ==> k0 == k1; } max == |a| == |b|; ==> { reveal SameUntilMax; } a == b; } } lemma LexIsTransitive(a: seq, b: seq, c: seq, less: (T, T) -> bool) requires Transitive(less) requires LexicographicLessOrEqual(a, b, less) requires LexicographicLessOrEqual(b, c, less) ensures LexicographicLessOrEqual(a, c, less) { var k0 :| 0 <= k0 <= |a| && LexicographicLessOrEqualAux(a, b, less, k0); var k1 :| 0 <= k1 <= |b| && LexicographicLessOrEqualAux(b, c, less, k1); var k := if k0 < k1 then k0 else k1; assert LexicographicLessOrEqualAux(a, c, less, k); } lemma LexIsTotal(a: seq, b: seq, less: (T, T) -> bool) requires Trich: Trichotomous(less) ensures LexicographicLessOrEqual(a, b, less) || LexicographicLessOrEqual(b, a, less) { var m := 0; while m < |a| && m < |b| && a[m] == b[m] invariant m <= |a| && m <= |b| invariant forall i :: 0 <= i < m ==> a[i] == b[i] { m := m + 1; } // m is the length of the common prefix of a and b if m == |a| == |b| { assert a == b; LexIsReflexive(a, less); } else if m == |a| < |b| { assert LexicographicLessOrEqualAux(a, b, less, m); } else if m == |b| < |a| { assert LexicographicLessOrEqualAux(b, a, less, m); } else { assert m < |a| && m < |b|; reveal Trich; if case less(a[m], b[m]) => assert LexicographicLessOrEqualAux(a, b, less, m); case less(b[m], a[m]) => assert LexicographicLessOrEqualAux(b, a, less, m); } } /* * SetToOrderedSequence(s, less) takes a set of T-strings and returns them as a sequence, * ordered by the lexicographic ordering whose underlying irreflexive ordering is "less". * The function is compilable, but will not exhibit enviable performance. */ function method {:tailrecursion} SetToOrderedSequence(s: set>, less: (T, T) -> bool): (q: seq>) requires Trichotomous(less) && Transitive(less) ensures |s| == |q| ensures forall i :: 0 <= i < |q| ==> q[i] in s ensures forall k :: k in s ==> k in q ensures forall i :: 0 < i < |q| ==> LexicographicLessOrEqual(q[i-1], q[i], less) ensures forall i, j | 0 <= i < j < |q| :: q[i] != q[j]; { if s == {} then [] else // In preparation for the assign-such-that statement below, we'll need to // prove that a minimum exists and that it is unique. // The following lemma shows existence: ThereIsAMinimum(s, less); // The following assertion shows uniqueness: assert forall a, b :: IsMinimum(a, s, less) && IsMinimum(b, s, less) ==> a == b by { // The proof of the assertion is the following forall statement. // But why did we even bother to write the assert-by instead of // just writing this forall statement in the first place? Because // we are in an expression context and a forall statement cannot start // an expression (because the "forall" makes the parser think that // a forall expression is coming). forall a, b | IsMinimum(a, s, less) && IsMinimum(b, s, less) { // For the given a and b, the proof is settled by calling the following lemma: MinimumIsUnique(a, b, s, less); } } // The "a in s" in the following line follows from IsMinimum(a, s), so it // is logically redundant. However, it is needed to convince the compiler // that the assign-such-that statement is compilable. var a :| a in s && IsMinimum(a, s, less); [a] + SetToOrderedSequence(s - {a}, less) } predicate method IsMinimum(a: seq, s: set>, less: (T, T) -> bool) { a in s && forall z :: z in s ==> LexicographicLessOrEqual(a, z, less) } lemma ThereIsAMinimum(s: set>, less: (T, T) -> bool) requires s != {} requires Trichotomous(less) && Transitive(less) ensures exists a :: IsMinimum(a, s, less) { var a := FindMinimum(s, less); } lemma MinimumIsUnique(a: seq, b: seq, s: set>, less: (T, T) -> bool) requires IsMinimum(a, s, less) && IsMinimum(b, s, less) requires Trichotomous(less) ensures a == b { LexIsAntisymmetric(a, b, less); } lemma FindMinimum(s: set>, less: (T, T) -> bool) returns (a: seq) requires s != {} requires Trichotomous(less) && Transitive(less) ensures IsMinimum(a, s, less) { a :| a in s; if s == {a} { LexIsReflexive(a, less); } else { var s' := s - {a}; assert forall x :: x in s <==> x == a || x in s'; var a' := FindMinimum(s', less); if LexicographicLessOrEqual(a', a, less) { a := a'; } else { assert LexicographicLessOrEqual(a, a', less) by { LexIsTotal(a, a', less); } forall z | z in s ensures LexicographicLessOrEqual(a, z, less) { if z == a { LexIsReflexive(a, less); } else { calc { true; == // z in s && z != a z in s'; ==> // by postcondition of FindMinim(s') above LexicographicLessOrEqual(a', z, less); ==> { LexIsTransitive(a, a', z, less); } LexicographicLessOrEqual(a, z, less); } } } } } } }