-
Notifications
You must be signed in to change notification settings - Fork 271
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
Implement decreases to
expressions
#5367
Changes from all commits
53a1a9d
14c8e49
e20e735
23eabb4
cd7b8d9
5fe0e36
3aa0609
30f81df
ee6059e
1757a58
bccfb40
59f74d0
341f8e8
bd4a58c
7b2a9f1
4ad5ae1
cc89ec1
ad26c21
bbd3828
7c4672e
31dbe3d
1faa65d
54e1b00
6f86ce9
cd93069
2c1a505
e87c990
e69bbfb
ca3fab5
76a7896
a1a3dcb
1856361
aefe775
1c6f182
e2700c3
a4c910a
089811a
5e84606
a4181a4
15d21a4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
|
||
namespace Microsoft.Dafny; | ||
|
||
public class DecreasesToExpr : Expression, ICloneable<DecreasesToExpr> { | ||
public IEnumerable<Expression> OldExpressions { get; } | ||
public IEnumerable<Expression> NewExpressions { get; } | ||
|
||
public bool AllowNoChange { get; } | ||
|
||
public DecreasesToExpr(IToken tok, IEnumerable<Expression> oldExpressions, IEnumerable<Expression> newExpressions, bool allowNoChange) : base(tok) { | ||
OldExpressions = oldExpressions; | ||
NewExpressions = newExpressions; | ||
AllowNoChange = allowNoChange; | ||
} | ||
|
||
public DecreasesToExpr(Cloner cloner, DecreasesToExpr original) : base(cloner, original) { | ||
OldExpressions = original.OldExpressions.Select(cloner.CloneExpr); | ||
NewExpressions = original.NewExpressions.Select(cloner.CloneExpr); | ||
AllowNoChange = original.AllowNoChange; | ||
} | ||
|
||
public DecreasesToExpr Clone(Cloner cloner) { | ||
return new DecreasesToExpr(cloner, this); | ||
} | ||
|
||
public override IEnumerable<Expression> SubExpressions { | ||
get { | ||
foreach (var oldExpr in OldExpressions) { | ||
yield return oldExpr; | ||
} | ||
foreach (var newExpr in NewExpressions) { | ||
yield return newExpr; | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -144,6 +144,7 @@ TOKENS | |
by = "by". | ||
in = "in". | ||
decreases = "decreases". | ||
nonincreases = "nonincreases". | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now that it's there, we might as well keep it, but was there a strong reason to add it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sometimes the check that Dafny needs to perform is, roughly speaking, <= instead of <. To be able to express those conditions, it seemed most natural to have a form of the expression that encoded <= instead of <. It would be possible, alternatively, to have |
||
invariant = "invariant". | ||
function = "function". | ||
predicate = "predicate". | ||
|
@@ -2970,7 +2971,7 @@ AssumeStmt<out Statement/*!*/ s> | |
.) | ||
"assume" (. x = t; startToken = t; .) | ||
{ Attribute<ref attrs> } | ||
( Expression<out e, false, true> | ||
( Expression<out e, false, false> | ||
| ellipsis (. dotdotdot = t; | ||
errors.Deprecated(ErrorId.p_deprecated_statement_refinement, t, "the ... refinement feature in statements is deprecated"); | ||
.) | ||
|
@@ -3202,6 +3203,47 @@ Forall = "forall". | |
Exists = "exists". | ||
QSep = "::". | ||
|
||
MaybeDecreasesToExpression<out Expression e, IToken lp> | ||
= (. List<ActualBinding> args = new(); | ||
List<Expression> newExprs = new(); | ||
Expression tmp = null; | ||
IToken x = null; | ||
bool allowNoChange = false; | ||
.) | ||
TupleArgs<args> | ||
[ ( "decreases" (. allowNoChange = false; .) | ||
| "nonincreases" (. allowNoChange = true; .) | ||
) (. x = t; .) | ||
ident | ||
(. if (t.val != "to") { | ||
SemErr(ErrorId.p_decreases_without_to, t, "expected 'to'"); | ||
} | ||
.) | ||
Expression<out tmp, false, false> (. newExprs.Add(tmp); .) | ||
{ "," | ||
Expression<out tmp, false, false> (. newExprs.Add(tmp); .) | ||
} | ||
] | ||
(. | ||
if (newExprs.Count() == 0) { | ||
e = ProcessTupleArgs(args, lp); | ||
} else if (args.Count() > 0 && newExprs.Count() > 0) { | ||
var formals = args.Where(arg => arg.FormalParameterName != null).ToList(); | ||
if (formals.Any()) { | ||
SemErr(ErrorId.p_binding_in_decreases_to, | ||
formals[0].FormalParameterName, | ||
"bindings are not allowed in `decreases to` expressions."); | ||
} | ||
var oldExprs = args.Select(arg => arg.Actual).ToList(); | ||
e = new DecreasesToExpr(x, oldExprs, newExprs, allowNoChange); | ||
e.RangeToken = new RangeToken(oldExprs[0].tok, newExprs[newExprs.Count() - 1].tok); | ||
} else { | ||
// Unreachable | ||
e = null; | ||
} | ||
.) | ||
. | ||
|
||
/* The "allowLemma" argument says whether or not the expression | ||
* to be parsed is allowed to have the form S;E where S is a call to a lemma. | ||
* "allowLemma" should be passed in as "false" whenever the expression to | ||
|
@@ -3718,43 +3760,23 @@ LambdaSpec<.ref List<FrameExpression> reads, ref Attributes readsAttrs, ref Expr | |
|
||
/*------------------------------------------------------------------------*/ | ||
ParensExpression<out Expression e> | ||
= (. IToken lp; IToken rp; | ||
= (. IToken lp = null; IToken rp = null; | ||
var args = new List<ActualBinding>(); | ||
e = dummyExpr; | ||
.) | ||
"(" (. lp = t; .) | ||
[ TupleArgs<args> ] | ||
")" (. rp = t; .) | ||
(. if (args.Count == 1 && !args[0].IsGhost) { | ||
if (args[0].FormalParameterName != null) { | ||
SemErr(ErrorId.p_no_parenthesized_binding, new RangeToken(lp,rp), "binding not allowed in parenthesized expression"); | ||
} | ||
e = new ParensExpression(lp, args[0].Actual); | ||
} else { | ||
// Compute the actual position of ghost arguments | ||
var ghostness = new bool[args.Count]; | ||
for (var i = 0; i < args.Count; i++) { | ||
ghostness[i] = false; | ||
} | ||
for (var i = 0; i < args.Count; i++) { | ||
var arg = args[i]; | ||
if (arg.IsGhost) { | ||
if (arg.FormalParameterName == null) { | ||
ghostness[i] = true; | ||
} else { | ||
var success = int.TryParse(arg.FormalParameterName.val, out var index); | ||
if (success && 0 <= index && index < args.Count) { | ||
ghostness[index] = true; | ||
} | ||
} | ||
} | ||
} | ||
var argumentGhostness = ghostness.ToList(); | ||
// make sure the corresponding tuple type exists | ||
SystemModuleModifiers.Add(b => b.TupleType(lp, args.Count, true, argumentGhostness)); | ||
e = new DatatypeValue(lp, SystemModuleManager.TupleTypeName(argumentGhostness), SystemModuleManager.TupleTypeCtorName(args.Count), args); | ||
} | ||
e.RangeToken = new RangeToken(lp, rp); | ||
.) | ||
"(" (. lp = t; .) | ||
( | ||
MaybeDecreasesToExpression<out e, lp> | ||
")" (. rp = t; | ||
if (e is not DatatypeValue) { | ||
e = new ParensExpression(lp, e); | ||
} | ||
.) | ||
| | ||
")" (. rp = t; | ||
e = ProcessTupleArgs(new List<ActualBinding>(), lp); | ||
.) | ||
) (. e.RangeToken = new RangeToken(lp, rp); .) | ||
. | ||
|
||
/*------------------------------------------------------------------------*/ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we test this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep. See the
Asserted expression:
lines in the added tests.