-
-
Notifications
You must be signed in to change notification settings - Fork 41
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
Level finder #811
Level finder #811
Conversation
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.
A few suggestions, but looks good to me overall.
case OperEx(op, args @ _*) | ||
if op == TlaActionOper.prime || op == TlaActionOper.enabled | ||
|| op == TlaActionOper.unchanged || op == TlaActionOper.stutter | ||
|| op == TlaActionOper.nostutter || op == TlaActionOper.composition => | ||
TlaLevelAction.join(args.map(find)) | ||
|
||
case OperEx(op, args @ _*) | ||
if op == TlaTempOper.box || op == TlaTempOper.diamond || op == TlaTempOper.leadsTo | ||
|| op == TlaTempOper.weakFairness || op == TlaTempOper.strongFairness | ||
|| op == TlaTempOper.guarantees || op == TlaTempOper.AA || op == TlaTempOper.EE => | ||
TlaLevelTemporal.join(args.map(find)) | ||
|
||
case OperEx(_, args @ _*) => | ||
TlaLevelConst.join(args.map(find)) |
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.
It could just be a matter of taste, but IMO this approach is a bit easier to read (assuming it's feasible):
case OperEx(op, args @ _*) | |
if op == TlaActionOper.prime || op == TlaActionOper.enabled | |
|| op == TlaActionOper.unchanged || op == TlaActionOper.stutter | |
|| op == TlaActionOper.nostutter || op == TlaActionOper.composition => | |
TlaLevelAction.join(args.map(find)) | |
case OperEx(op, args @ _*) | |
if op == TlaTempOper.box || op == TlaTempOper.diamond || op == TlaTempOper.leadsTo | |
|| op == TlaTempOper.weakFairness || op == TlaTempOper.strongFairness | |
|| op == TlaTempOper.guarantees || op == TlaTempOper.AA || op == TlaTempOper.EE => | |
TlaLevelTemporal.join(args.map(find)) | |
case OperEx(_, args @ _*) => | |
TlaLevelConst.join(args.map(find)) | |
case OperEx(op, args @ _*) => | |
val ctor = op match { | |
case TlaActionOper.prime | TlaActionOper.enabled | |
| TlaActionOper.unchanged | TlaActionOper.stutter | |
| TlaActionOper.nostutter | TlaActionOper.composition => TlaLevelAction | |
case TlaTempOper.box | TlaTempOper.diamond | |
| TlaTempOper.leadsTo | TlaTempOper.weakFairness | |
| TlaTempOper.strongFairness | TlaTempOper.guarantees | |
| TlaTempOper.AA | TlaTempOper.EE => TlaLevelTemporal | |
case _ => TlaLevelConst | |
} | |
ctor.join(args.map(find)) |
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.
Good point. I think I will just introduce a map.
case _ => | ||
TlaLevelConst |
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.
Could this catchall come back to bite us if new expressions are introduced? Would it be worth while to explicitly enumerate the other constructors here instead of having a wildcard?
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.
I doubt that, as the types of expressions were stable for long time.
|
||
case TlaOperDecl(name, _, _) => | ||
cacheLevel(Set(name), name) | ||
levelCacheMap(name) |
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.
Given that we are caching results, why don't we first check whether the name
is cached before we try to add it to the cache?
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, added that
case TlaConstDecl(_) => | ||
TlaLevelConst | ||
|
||
case TlaVarDecl(_) => | ||
TlaLevelState | ||
|
||
case TlaAssumeDecl(_) => | ||
TlaLevelConst | ||
|
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.
unless I missunderstand, it seems like where checking for this and then determining the level in two parallel ways, Once here by pattern matching, and once when we construct the level finder, when we cache to the various sets. Couldn't we handle all of this logic with the _.contains
checks and the caching lookup?
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.
The code in apply
is also handling the cases of ASSUME
and THEOREM
. Also, this code avoids an extra lookup in defs
, consts
and vars
(but that's not the main point).
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.
What happens if I write an assume declaration with primes?
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.
SANY will tell you that it does not like your spec. I found that out a couple of months ago :D
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.
It will even complain if you use state variables in ASSUME
.
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.
Ok, what about the expression (1 + 1)'
outside of assume? Just because it has a prime operator, does not mean its level isn't 0.
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.
Well, syntactically (1 + 1)'
is level 2. Semantically, it is level 0. It looks like the book is not precise about whether we should compute the levels semantically or syntactically.
} | ||
|
||
/** | ||
* Join the levels by computing the smallest level that covers both of `this` and `that`. |
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.
I'm surprised that joining gives the least, rather than greatest level. Is this because its actually more difficult to handle , e.g., constants than handling temporal formulas?
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.
no-no, it's actually the maximum of the two levels, just badly formulated
private val INT_TO_LEVEL = Seq(TlaLevelConst, TlaLevelState, TlaLevelAction, TlaLevelTemporal) | ||
|
||
def fromInt(level: Int): TlaLevel = { | ||
if (level < 0 || level >= INT_TO_LEVEL.length) { | ||
throw new IllegalArgumentException(s"Unexpected level of TlaValue: $level") | ||
} else { | ||
INT_TO_LEVEL(level) | ||
} | ||
} |
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.
Since we already have the Int
values inhe Const
objects, why not just have fromInt
give level.level
? It seems like this duplicates the logic: we record the int once as a position in the sequence and another time explicitly in the level
attribute.
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.
Good point. Fixed that.
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.
LGTM
private val INT_TO_LEVEL = Seq(TlaLevelConst, TlaLevelState, TlaLevelAction, TlaLevelTemporal) | ||
|
||
def fromInt(levelNo: Int): TlaLevel = { | ||
INT_TO_LEVEL.find(_.level == levelNo) match { |
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.
Minor gripe,I would use an indexed collection and do boundary tests here, instead of find.
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.
I've replaced it with find, which should be ok for a list of 4 elements
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.
Ha-ha. My solution was exactly as you suggest, and @shonfeder did not like it :P
This PR implements computation of TLA+ levels (that are described in Specifying Systems). In a nutshell, a TLA+ expression is assigned one of the four levels:
The concept of levels may be reused in other code. That's why it is placed in tlair. The code in this PR is required in #810.
make fmt-fix
(or had formatting run automatically on all files edited)Documentation added for any new functionality