In Dafny, I'm working with an iterative approach to find all instances of a given substring and replace them with another given substring.
method findAndReplace(str:string, toFind:string, toReplace:string) returns (newString:string)
requires |toFind| > 0
ensures newString == verify(str, toFind, toReplace)
{
newString := "";
var i := 0;
while (i < |str|)
invariant i <= |str|
invariant newString == verify(str[..i], toFind, toReplace) // flagged as not maintained by loop
decreases |str| - i
{
if( toFind <= str[i..]) {
newString := newString + toReplace;
i := i + |toFind|;
}
else {
var next := str[i..i+1];
newString := newString + next;
i := i + 1;
}
}
return newString;
}
Included in the ensures and invariant is a call to a function verify
meant to help verification along.
function verify(str:string, toFind:string, toReplace:string) : string
requires |toFind| > 0
decreases str
{
if |str| < |toFind|
then
str
else
if str[..|toFind|] == toFind
then
toReplace + verify(str[|toFind|..], toFind, toReplace)
else
str[..1] + verify(str[1..], toFind, toReplace)
}
Except it doesn't verify. Specifically, the invariant is flagged as not maintained by the loop.
My question is this: Is it due to difference in control flow between the iterative method and the recursive function that dafny is unable to connect them? If I were to somehow come up with an iterative function that does practically the same thing as verify
, would dafny have an easier time verifying the invariant?
What really makes me think this is the case is because I have a recursive method that works just fine with verify
method findAndReplace(str : string, toFind : string, toReplace : string) returns (newString : string)
requires |toFind| > 0
ensures newString == verify(str, toFind, toReplace)
decreases str
{
if (|str| < |toFind|)
{
return str;
}
if (toFind <= str)
{
var result := findAndReplace(str[|toFind|..], toFind, toReplace);
return toReplace + result;
}
else
{
var result := findAndReplace(str[1..], toFind, toReplace);
return str[..1] + result;
}
}
It seems like its important that a method and its corresponding function should follow the same basic structure for easier verification, but I couldn't find any such imperative in the dafny docs. The other part of me says as long as they give identical output on any input, dafny should see their behavior as identical.