Skip to content
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

Merged
merged 8 commits into from
May 6, 2021
Merged

Level finder #811

merged 8 commits into from
May 6, 2021

Conversation

konnov
Copy link
Collaborator

@konnov konnov commented May 6, 2021

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:

  • Level 0: the expression is only using constants.
  • Level 1: the expression is using constants, state variables, but no action or temporal operators.
  • Level 2: the expression is using action operators, but no temporal operators.
  • Level 3: the expression using temporal operators.

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.

  • Tests added for any new code
  • Ran make fmt-fix (or had formatting run automatically on all files edited)
  • Documentation added for any new functionality
  • Entry added to UNRELEASED.md for any new functionality

@konnov konnov requested review from shonfeder and Kukovec May 6, 2021 12:57
Copy link
Contributor

@shonfeder shonfeder left a 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.

Comment on lines 18 to 31
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))
Copy link
Contributor

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):

Suggested change
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))

Copy link
Collaborator Author

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.

Comment on lines +36 to +37
case _ =>
TlaLevelConst
Copy link
Contributor

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?

Copy link
Collaborator Author

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)
Copy link
Contributor

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?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, added that

Comment on lines +17 to +25
case TlaConstDecl(_) =>
TlaLevelConst

case TlaVarDecl(_) =>
TlaLevelState

case TlaAssumeDecl(_) =>
TlaLevelConst

Copy link
Contributor

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?

Copy link
Collaborator Author

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).

Copy link
Collaborator

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?

Copy link
Collaborator Author

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

Copy link
Collaborator Author

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.

Copy link
Collaborator

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.

Copy link
Collaborator Author

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`.
Copy link
Contributor

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?

Copy link
Collaborator Author

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

Comment on lines 51 to 59
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)
}
}
Copy link
Contributor

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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Fixed that.

@konnov konnov enabled auto-merge (squash) May 6, 2021 18:19
@konnov konnov merged commit 95ebc85 into unstable May 6, 2021
Copy link
Collaborator

@Kukovec Kukovec left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment on lines +51 to +54
private val INT_TO_LEVEL = Seq(TlaLevelConst, TlaLevelState, TlaLevelAction, TlaLevelTemporal)

def fromInt(levelNo: Int): TlaLevel = {
INT_TO_LEVEL.find(_.level == levelNo) match {
Copy link
Collaborator

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.

Copy link
Collaborator Author

@konnov konnov May 6, 2021

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

Copy link
Collaborator Author

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

@konnov konnov deleted the igor/levels branch May 6, 2021 18:41
@apalache-bot apalache-bot mentioned this pull request May 10, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants