Make some more progress on the Alloy article.
This commit is contained in:
parent
9ae4798d80
commit
384f5de765
|
@ -10,8 +10,7 @@ sig Bitfield {
|
|||
|
||||
/* A filter state has filterFlags and excludeFlags, both represented as conjunctions. */
|
||||
sig FilterState {
|
||||
, include: Bitfield
|
||||
, exclude: Bitfield
|
||||
, curFilter: Bitfield
|
||||
}
|
||||
|
||||
/* Initially, no search has happeneed for a scope, so its 'found' is not set to anything. */
|
||||
|
@ -111,41 +110,43 @@ matchingBitfieldExists3: run {
|
|||
}
|
||||
}
|
||||
|
||||
pred configureState[filterState: FilterState] {
|
||||
pred possibleState[filterState: FilterState] {
|
||||
some initialState: FilterState {
|
||||
// Each lookup in scope starts with empty filter and exclude flags
|
||||
bitfieldEmpty[initialState.include] and bitfieldEmpty[initialState.exclude]
|
||||
// Each lookup in scope starts with empty filter flags
|
||||
bitfieldEmpty[initialState.curFilter]
|
||||
|
||||
// The intermediate states (bf1) are used for sequencing of operations.
|
||||
some bf1 : Bitfield {
|
||||
// The intermediate states (bitfieldMiddle) are used for sequencing of operations.
|
||||
some bitfieldMiddle : Bitfield {
|
||||
// Add "Public" depending on skipPrivateVisibilities
|
||||
addBitfieldFlag[initialState.include, bf1, Public] or
|
||||
bitfieldEqual[initialState.include, bf1]
|
||||
addBitfieldFlag[initialState.curFilter, bitfieldMiddle, Public] or
|
||||
bitfieldEqual[initialState.curFilter, bitfieldMiddle]
|
||||
|
||||
// If it's a method receiver, add method or field restriction
|
||||
addBitfieldFlag[bf1, filterState.include, MethodOrField] or
|
||||
addBitfieldFlag[bitfieldMiddle, filterState.curFilter, MethodOrField] or
|
||||
// if it's not a receiver, filter to non-methods (could be overridden)
|
||||
addBitfieldFlagNeg[bf1, filterState.include, Method] or
|
||||
// Maybe methods are not being included but it's not a receiver, so no change.
|
||||
bitfieldEqual[bf1, filterState.include]
|
||||
addBitfieldFlagNeg[bitfieldMiddle, filterState.curFilter, Method] or
|
||||
// Maybe methods are not being curFilterd but it's not a receiver, so no change.
|
||||
bitfieldEqual[bitfieldMiddle, filterState.curFilter]
|
||||
}
|
||||
// Exclude filter doesn't change here
|
||||
initialState.exclude = filterState.exclude
|
||||
}
|
||||
}
|
||||
|
||||
pred oldUpdate[toSet: Bitfield + NotSet, setTo: FilterState] {
|
||||
toSet' in Bitfield and bitfieldIntersection[toSet, setTo.include, toSet']
|
||||
possibleStateExists: run {
|
||||
some filterState : FilterState | possibleState[filterState] and #filterState.curFilter.positiveFlags = 1
|
||||
}
|
||||
|
||||
pred update[toSet: Bitfield + NotSet, setTo: FilterState] {
|
||||
toSet' in Bitfield and bitfieldIntersection[toSet, setTo.curFilter, toSet']
|
||||
}
|
||||
|
||||
pred newUpdate[toSet: Bitfield + NotSet, setTo: FilterState] {
|
||||
(not bitfieldIncomparable[toSet, setTo.include] and oldUpdate[toSet, setTo]) or
|
||||
(bitfieldIncomparable[toSet, setTo.include] and toSet = toSet')
|
||||
(not bitfieldIncomparable[toSet, setTo.curFilter] and update[toSet, setTo]) or
|
||||
(bitfieldIncomparable[toSet, setTo.curFilter] and toSet = toSet')
|
||||
}
|
||||
|
||||
pred updateOrSet[toSet: Bitfield + NotSet, setTo: FilterState] {
|
||||
(toSet in NotSet and toSet' = setTo.include) or
|
||||
(toSet not in NotSet and oldUpdate[toSet, setTo])
|
||||
(toSet in NotSet and toSet' = setTo.curFilter) or
|
||||
(toSet not in NotSet and update[toSet, setTo])
|
||||
}
|
||||
|
||||
pred excludeBitfield[found: Bitfield + NotSet, exclude: Bitfield] {
|
||||
|
@ -163,7 +164,7 @@ fact step {
|
|||
all searchState: SearchState {
|
||||
some fs: FilterState {
|
||||
// This is a possible combination of lookup flags
|
||||
configureState[fs]
|
||||
possibleState[fs]
|
||||
|
||||
// If a search has been performed before, take the intersection; otherwise,
|
||||
// just insert the current filter flags.
|
||||
|
@ -178,21 +179,21 @@ example: run {
|
|||
// a way that subsequent results of searching it will miss things.
|
||||
eventually some symbol: Symbol, fs: FilterState, fsBroken: FilterState, exclude: Bitfield {
|
||||
// Some search (fs) will cause a transition / modification of the search state...
|
||||
configureState[fs]
|
||||
possibleState[fs]
|
||||
updateOrSet[searchState.found, fs]
|
||||
// Such that a later, valid search... (fsBroken)
|
||||
configureState[fsBroken]
|
||||
possibleState[fsBroken]
|
||||
excludeBitfield[searchState.found', exclude]
|
||||
|
||||
// Will allow for a symbol ...
|
||||
// ... that are left out of the original search...
|
||||
not bitfieldMatchesProperties[searchState.found, symbol]
|
||||
// ... and out of the current search
|
||||
not (bitfieldMatchesProperties[fs.include, symbol] and not bitfieldMatchesProperties[searchState.found, symbol])
|
||||
not (bitfieldMatchesProperties[fs.curFilter, symbol] and not bitfieldMatchesProperties[searchState.found, symbol])
|
||||
// But would be matched by the broken search...
|
||||
bitfieldMatchesProperties[fsBroken.include, symbol]
|
||||
bitfieldMatchesProperties[fsBroken.curFilter, symbol]
|
||||
// ... to not be matched by a search with the new state:
|
||||
not (bitfieldMatchesProperties[fsBroken.include, symbol] and not bitfieldMatchesProperties[exclude, symbol])
|
||||
not (bitfieldMatchesProperties[fsBroken.curFilter, symbol] and not bitfieldMatchesProperties[exclude, symbol])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -205,7 +205,7 @@ mathematically, and have a model checker generate outlandish scenarios for
|
|||
me. Having at some point seen [Hillel Wayne's
|
||||
post](https://www.hillelwayne.com/post/alloy6/) about the release of
|
||||
[Alloy 6](https://alloytools.org/), I thought I'd give it a go. I'd never
|
||||
touched alloy before this, so be warned: this is what I came up with on my
|
||||
touched Alloy before this, so be warned: this is what I came up with on my
|
||||
own attempt.
|
||||
|
||||
### Modeling Flags and Bitsets in Alloy
|
||||
|
@ -245,13 +245,13 @@ some condition holds for some arguments. This might seem abstract; as an
|
|||
example, here's `bitfieldEmpty`, which checks that a bitfield has no flags
|
||||
set.
|
||||
|
||||
{{< codelines "Alloy" "dyno-alloy/DynoAlloy.als" 27 29 >}}
|
||||
{{< codelines "Alloy" "dyno-alloy/DynoAlloy.als" 26 28 >}}
|
||||
|
||||
The `#` operator in Alloy is used to check the size of a set. So, to check
|
||||
if a bitfield is empty, I simply check if there are neither positive nor
|
||||
negative flags. Probably the most unusual aspect of this piece of code is
|
||||
that equality is written as `=`, as opposed to `==` like in most common
|
||||
languages. This is because, like I said, alloy is all about relations and
|
||||
languages. This is because, like I said, Alloy is all about relations and
|
||||
predicates, and not at all about imperative manipulation of data. So,
|
||||
there's no need to reserve `=` for assignment.
|
||||
|
||||
|
@ -259,7 +259,7 @@ The next step from here is a predicate that accepts two arguments,
|
|||
`bitfieldEqual`. As its name suggests, this predicate accepts two
|
||||
bitfields, and makes sure they have exactly the same flags set.
|
||||
|
||||
{{< codelines "Alloy" "dyno-alloy/DynoAlloy.als" 31 33 >}}
|
||||
{{< codelines "Alloy" "dyno-alloy/DynoAlloy.als" 30 32 >}}
|
||||
|
||||
So far, this has been pretty similar to just writing boolean functions in a
|
||||
language like C++. However, the similarity is only superficial. An easy way
|
||||
|
@ -281,7 +281,7 @@ _does_ the output of a bitfield intersection connect to the two operands
|
|||
being intersected? Well, its two flag sets will be intersections of the
|
||||
flag sets of the inputs!
|
||||
|
||||
{{< codelines "Alloy" "dyno-alloy/DynoAlloy.als" 35 38 >}}
|
||||
{{< codelines "Alloy" "dyno-alloy/DynoAlloy.als" 34 37 >}}
|
||||
|
||||
Next, let's talk about what flags _do_. They are used to include and
|
||||
exclude symbols based on certain properties. One property is being a
|
||||
|
@ -293,9 +293,9 @@ can have multiple properties (e.g., a public method). Unlike our bitfields,
|
|||
though, we won't be modeling symbols as having both positive and negative
|
||||
properties. That is to say, we won't have a "not public" property: the
|
||||
absence of the "public" property will be enough to make something private.
|
||||
Here's the alloy definition for everything I just said:
|
||||
Here's the Alloy definition for everything I just said:
|
||||
|
||||
{{< codelines "Alloy" "dyno-alloy/DynoAlloy.als" 60 64 >}}
|
||||
{{< codelines "Alloy" "dyno-alloy/DynoAlloy.als" 59 63 >}}
|
||||
|
||||
Now, we can specify how flags in a bitfield relates to properties on a
|
||||
symbol. We can do so by saying which flags match which properties. The
|
||||
|
@ -304,7 +304,7 @@ The `MethodOrField` flag is more lenient, and will be satisfied by either
|
|||
`PMethod` or `PField`. Here's a predicate `flagMatchesProperty` that
|
||||
encodes all the flag-property combinations:
|
||||
|
||||
{{< codelines "Alloy" "dyno-alloy/DynoAlloy.als" 66 70 >}}
|
||||
{{< codelines "Alloy" "dyno-alloy/DynoAlloy.als" 65 69 >}}
|
||||
|
||||
A bitfield matching a symbol is a little bit more complicated. Said
|
||||
informally, the condition for a bitfield matching a symbol is twofold:
|
||||
|
@ -319,20 +319,20 @@ informally, the condition for a bitfield matching a symbol is twofold:
|
|||
|
||||
Each of the above two conditions translate quite literally into Alloy:
|
||||
|
||||
{{< codelines "Alloy" "dyno-alloy/DynoAlloy.als" 72 75 >}}
|
||||
{{< codelines "Alloy" "dyno-alloy/DynoAlloy.als" 71 74 >}}
|
||||
|
||||
We can read line 73 as "for each flag in a bitfield's positive flags, there
|
||||
must be some property in the symbol that matches it". Similarly, line 74
|
||||
can be read out loud as "for each flag in the negative flags, no property
|
||||
in the symbol must match it".
|
||||
|
||||
We've written a fair bit of alloy. If you're anything like me, you might be
|
||||
We've written a fair bit of Alloy. If you're anything like me, you might be
|
||||
getting a bit twitchy: how do we even check that any of this works? For
|
||||
this, we'll need to run our model. We will give Alloy a claim, and ask
|
||||
it to find a situation in which that claim holds true. The simplest claim
|
||||
is "there exists a bitfield".
|
||||
|
||||
{{< codelines "Alloy" "dyno-alloy/DynoAlloy.als" 77 79 >}}
|
||||
{{< codelines "Alloy" "dyno-alloy/DynoAlloy.als" 76 78 >}}
|
||||
|
||||
Executing this model yields a pretty interesting bitfield: one in which every single flag is set -- both the positive and negative versions.
|
||||
|
||||
|
@ -343,7 +343,7 @@ You can't be and not be a method at the same time, for instance. For for a
|
|||
more interesting example, let's ask for a bitfield that matches some
|
||||
symbol.
|
||||
|
||||
{{< codelines "Alloy" "dyno-alloy/DynoAlloy.als" 81 83 >}}
|
||||
{{< codelines "Alloy" "dyno-alloy/DynoAlloy.als" 80 82 >}}
|
||||
|
||||
The output here is pretty interesting too. Alloy finds a symbol and a
|
||||
bitfield that matches it, but they're both empty. In effect, it said:
|
||||
|
@ -357,31 +357,31 @@ Let's try nudge it towards a more interesting case. I'm going to ask for a
|
|||
filter with one positive and one negative flag, and a symbol with two
|
||||
properties.
|
||||
|
||||
{{< codelines "Alloy" "dyno-alloy/DynoAlloy.als" 85 92 >}}
|
||||
{{< codelines "Alloy" "dyno-alloy/DynoAlloy.als" 84 91 >}}
|
||||
|
||||
The results are more interesting this time: we get a filter for private
|
||||
methods, and a private symbol that was... both a field and a method?
|
||||
|
||||
{{< figure src="matching_bitfield_exists_2.png" caption="Alloy's spiced up output satisfying \"a bit field that matches a symbol exists\"" >}}
|
||||
|
||||
We never told alloy that a symbol can't be both a field and a method. It
|
||||
We never told Alloy that a symbol can't be both a field and a method. It
|
||||
had no idea what the flags meant, just that they exist.
|
||||
To let Alloy know what we do -- that the two properties are incompatible --
|
||||
we can use a _fact_. To me, the most natural way of phrasing this is "there
|
||||
is never a symbol that has both the method and field properties". Alas,
|
||||
Alloy doesn't have a `never` keyword; it only has `always`. So I opt instead
|
||||
for an alternative formulation: "there are always zero symbols that are
|
||||
both methods and fields". In alloy, the claim looks like this:
|
||||
both methods and fields". In Alloy, the claim looks like this:
|
||||
|
||||
{{< codelines "Alloy" "dyno-alloy/DynoAlloy.als" 94 99 >}}
|
||||
{{< codelines "Alloy" "dyno-alloy/DynoAlloy.als" 93 98 >}}
|
||||
|
||||
Re-running the example program with this fact, alloy spits out a filter for
|
||||
Re-running the example program with this fact, Alloy spits out a filter for
|
||||
public non-method symbols, and a symbol that's a public field. Public
|
||||
fields also aren't a thing in Chapel (all fields in a class are publicly
|
||||
readable in the current version of the language). Perhaps it's time for
|
||||
another fact.
|
||||
|
||||
{{< codelines "Alloy" "dyno-alloy/DynoAlloy.als" 100 104 >}}
|
||||
{{< codelines "Alloy" "dyno-alloy/DynoAlloy.als" 99 103 >}}
|
||||
|
||||
But now, Alloy fails to come up with anything at all. That makes sense: by
|
||||
restricting the search to a symbol with two properties, and making `PField`
|
||||
|
@ -392,13 +392,255 @@ method, so making any of them negative would guarantee that our symbol
|
|||
would not be found. Let's change the example up a bit to only ask for
|
||||
positive flags.
|
||||
|
||||
{{< codelines "Alloy" "dyno-alloy/DynoAlloy.als" 106 112 >}}
|
||||
{{< codelines "Alloy" "dyno-alloy/DynoAlloy.als" 105 111 >}}
|
||||
|
||||
This time, alloy gives us a symbol that's a public method, and a filter
|
||||
This time, Alloy gives us a symbol that's a public method, and a filter
|
||||
that only looks for public methods. Fair enough.
|
||||
|
||||
{{< figure src="matching_bitfield_exists_3.png" caption="Alloy's spiced up output satisfying \"a bit field that matches a symbol exists\"" >}}
|
||||
|
||||
### Exploring Possible Search Configurations
|
||||
So now we have a descriptioin of filters and symbols in scopes. The next thing
|
||||
on the itinerary is modeling how the filters (include and exclude) are
|
||||
configured during scope search in Dyno. For this, let's take a look at the
|
||||
C++ code in Dyno.
|
||||
|
||||
I'll be using the branch that I was working on at the time of trying to
|
||||
apply Alloy. First, here's the code in C++ that defines the various flags
|
||||
I'd be working with (though I've omitted flags that are not currently
|
||||
used in the implementation).
|
||||
|
||||
```C++
|
||||
enum {
|
||||
/** Public */
|
||||
PUBLIC = 1,
|
||||
/** Not public (aka private) */
|
||||
NOT_PUBLIC = 2,
|
||||
/** A method or field declaration */
|
||||
METHOD_FIELD = 4,
|
||||
/** Something other than (a method or field declaration) */
|
||||
NOT_METHOD_FIELD = 8,
|
||||
/** A method declaration */
|
||||
METHOD = 64,
|
||||
/** Something other than a method declaration */
|
||||
NOT_METHOD = 128,
|
||||
};
|
||||
```
|
||||
|
||||
These are the flags that we model using a `Bitset`: `PUBLIC`,
|
||||
`METHOD_FIELD`, and `METHOD` are modeled using `positiveFlags`, and
|
||||
`NOT_PUBLIC`, `NOT_METHOD_FIELD`, and `NOT_METHOD` are modeled using
|
||||
`negativeFlags`. There are a lot of flags here, and it's not hard to
|
||||
imagine that _some_ combination of these flags will cause problems in our
|
||||
system (particularly when we _know_ it's an approximation). However, the
|
||||
flags aren't used arbitrarily; in fact, it wasn't too hard to track down the
|
||||
most important place in the code where bitsets are built.
|
||||
|
||||
```C++{linenos=true, linenostart=914}
|
||||
IdAndFlags::Flags curFilter = 0;
|
||||
/* ... some unrelated code ... */
|
||||
if (skipPrivateVisibilities) {
|
||||
curFilter |= IdAndFlags::PUBLIC;
|
||||
}
|
||||
if (onlyMethodsFields) {
|
||||
curFilter |= IdAndFlags::METHOD_FIELD;
|
||||
} else if (!includeMethods && receiverScopes.empty()) {
|
||||
curFilter |= IdAndFlags::NOT_METHOD;
|
||||
}
|
||||
```
|
||||
|
||||
The above code converts the current search parameters into `Bitfield`
|
||||
flags. For instance, if a `use` statement is being processed that doesn't
|
||||
have access to private fields, `skipPrivateVisibilities` will be set.
|
||||
On the other hand, if the calling code didn't explicitly ask for methods,
|
||||
and if there's no method receiver, then the last condition will be true.
|
||||
These various conditions are converted into bits and applied to
|
||||
`curFilter`. Then, `curFilter` is used for looking up symbols in a scope.
|
||||
|
||||
It's not too hard to model this by just looking at the code, and
|
||||
enumerating the possibilities. The first `if` statement can either be true
|
||||
or false, and then the subsequent `if`-`else` chain creates three
|
||||
possibilities in each case: either `METHOD_FIELD` is set, or `NOT_METHOD`,
|
||||
or nothing.
|
||||
|
||||
However, I envisioned this condition to possibly grow in complexity as more
|
||||
search configurations became necessary (in that, the `NOT_METHOD` option
|
||||
was an addition in my new branch). I therefore chose to model the possible
|
||||
`Bitfield` values more faithfully, by mimicking the imperative C++ code.
|
||||
|
||||
{{< dialog >}}
|
||||
{{< message "question" "reader" >}}
|
||||
Wait, something sounds off. Just earlier, you said Alloy "is not at all about
|
||||
imperative manipulation of data". But now, we're going to mimic plain
|
||||
imperative C++ code?
|
||||
{{< /message >}}
|
||||
{{< message "answer" "Daniel" >}}
|
||||
Alloy the programming language is still not imperative. However, we can
|
||||
<em>model</em> imperative behavior in Alloy. The way I see it, doing so
|
||||
requires us to venture a tiny bit into the realm of <em>semantics</em>
|
||||
for programming languages, in particular for imperative languages. This
|
||||
"venture" is very minimal though, and you really don't need to know much
|
||||
about semantics to understand it.
|
||||
{{< /message >}}
|
||||
{{< message "question" "reader" >}}
|
||||
Alright. How does one model imperative behavior in Alloy?
|
||||
{{< /message >}}
|
||||
{{< message "answer" "Daniel" >}}
|
||||
On to that next.
|
||||
{{< /message >}}
|
||||
{{< /dialog >}}
|
||||
|
||||
The essential piece of insight to modeling an imperative language, though
|
||||
it sounds a little bit tautological, is that _statements are all about
|
||||
manipulating state_. For example, state could be the value of a variable.
|
||||
If you start with the variable `x` storing the number `6`, and then execute
|
||||
the statement `x = x * 7`, the final value of `x` will be `42`. Thus, state
|
||||
has changed. To put this in terms Alloy would understand -- relations and
|
||||
sets -- a statement connects (relates) states before it's executed to states
|
||||
after it's executed. In our particular example, the connection would
|
||||
between the state `x = 6` and the state `x = 42`. In the case of adding the
|
||||
`PUBLIC` to `curFilter`, as on line 917 in the above code block, we could
|
||||
state the relationship as follows:
|
||||
|
||||
```Alloy
|
||||
addBitfieldFlag[bitfieldBefore, bitfieldAfter, Public]
|
||||
```
|
||||
|
||||
The above code states that `bitfieldAfter` (the state _after_ line 917) is
|
||||
the same `Bitfield` as `bitfieldBefore` (the state _before_ line 917), except
|
||||
that the `Public` flag has been added to it.
|
||||
|
||||
Things are a little more complicated when it comes to modeling the whole
|
||||
`if`-statement on line 916. If we wanted to be very precise, we'd need to
|
||||
encode the other variables (such as `skipPrivateVisibilities`), how they're
|
||||
set, and what values are possible. However, for the sake of keeping the
|
||||
scope of this model manageable for the time being, I'm content to do
|
||||
something simpler -- that is, acknowledge that the code on line 917 may or
|
||||
may not run. If it does run, our previous `addBitfieldFlag` will be the
|
||||
correct restriction on the before and after states. However, if it doesn't,
|
||||
the state shouldn't change at all. Therefore, we can model lines 916
|
||||
through 918 as follows (notice the `or`):
|
||||
|
||||
```Alloy
|
||||
addBitfieldFlag[bitfieldBefore, bitfieldAfter, Public] or
|
||||
bitfieldEqual[bitfieldBefore, bitfieldAfter]
|
||||
```
|
||||
|
||||
The next thing to note is that there are two `if` statements one after another.
|
||||
The state "after" the first statement is one and the same as the state
|
||||
"before" the second statement. Using arrows to represent the "before-after"
|
||||
relationship created by each statement, we can visualize the whole
|
||||
situation as follows:
|
||||
|
||||
{{< latex >}}
|
||||
\text{initial state}
|
||||
\xRightarrow{\text{first statement}}
|
||||
\text{middle state}
|
||||
\xRightarrow{\text{second statement}}
|
||||
\text{final state}
|
||||
{{< /latex >}}
|
||||
|
||||
We'll write our Alloy code to match:
|
||||
|
||||
```Alloy
|
||||
/* First if statement */
|
||||
addBitfieldFlag[bitfieldBefore, bitfieldMiddle, Public] or
|
||||
bitfieldEqual[bitfieldBefore, bitfieldMiddle]
|
||||
|
||||
/* ... something connecting bitfieldMiddle and bitfieldAfter ... */
|
||||
```
|
||||
|
||||
From here, we can handle the second `if`/`else` chain in the same way we
|
||||
did the first `if`-statement: by making all three outcomes of the chain be
|
||||
possible, and creating an `or` of all of them.
|
||||
|
||||
```Alloy
|
||||
/* First if statement */
|
||||
addBitfieldFlag[bitfieldBefore, bitfieldMiddle, Public] or
|
||||
bitfieldEqual[bitfieldBefore, bitfieldMiddle]
|
||||
|
||||
/* Second if statement */
|
||||
addBitfieldFlag[bitfieldMiddle, bitfieldAfter, MethodOrField] or
|
||||
addBitfieldFlagNeg[bitfieldMiddle, bitfieldAfter, Method] or
|
||||
bitfieldEqual[bitfieldMiddle, bitfieldAfter]
|
||||
```
|
||||
|
||||
So that helps model the relevant Dyno code. However, what we really want is
|
||||
an Alloy predicate that classifies possible outcomes of the piece of code:
|
||||
is a particular combination of flags possible or not? Here's the piece of
|
||||
Alloy that does so:
|
||||
|
||||
{{< codelines "Alloy" "dyno-alloy/DynoAlloy.als" 113 132 >}}
|
||||
|
||||
The `FilterState` on the first line (and elsewhere, really), is new. I'm
|
||||
trying to be explicit about the state in this particular computation. Its
|
||||
definition is very simple: currently, the only state we care about is the
|
||||
`Bitfield` corresponding to `curFilter` in the C++ code above.
|
||||
|
||||
{{< codelines "Alloy" "dyno-alloy/DynoAlloy.als" 12 14 >}}
|
||||
|
||||
There's not much more to the predicate. It says, in English, that a state
|
||||
`filterState` is possible if, starting from an empty initial state
|
||||
`initialState`, the model of our C++ code can end up with its particular
|
||||
set of flags in the `curFilter` bitfield.
|
||||
|
||||
Next, I needed to model the behavior the I described earlier: searching for
|
||||
\\(A \\land \\lnot B\\), and taking the intersection of two past searches
|
||||
when running subsequent searches.
|
||||
|
||||
Dyno implemented this roughly as follows:
|
||||
|
||||
1. It kept a mapping of (searched scope → search bitfield). Initially, this
|
||||
mapping was empty.
|
||||
2. When a scope was searched for the first time, its `curFilter` / search
|
||||
bitfield was stored into the mapping.
|
||||
3. When a scope was searched after that, the previously-stored flags in the
|
||||
mapping were excluded (that's the \\(A\\land\\lnot B\\) behavior), and
|
||||
the bitfield in the mapping was updated to be the intersection of
|
||||
`curFilter` and the stored flags.
|
||||
|
||||
We'll simplify the model by doing away with the mapping, and considering
|
||||
only a single scope that is searched many times. We'll represent the stored
|
||||
flags as a field `found`, which will be one of two things: either a
|
||||
`Bitfield` representing the previously-stored search configuration, or a
|
||||
`NotSet` sentinel value, representing a scope that hasn't been searched
|
||||
yet. The Alloy code:
|
||||
|
||||
{{< codelines "alloy" "dyno-alloy/dynoalloy.als" 21 23 >}}
|
||||
|
||||
The `NotSet` sentinel value is defined in a very simple way:
|
||||
|
||||
{{< codelines "Alloy" "dyno-alloy/DynoAlloy.als" 17 17 >}}
|
||||
|
||||
Both of these signatures use a new keyword, `one`. This keyword means that
|
||||
there's only a single instance of both `NotSet` and `SearchState` in our
|
||||
model. This is in contrast to a signature like `Bitfield,` which allows
|
||||
multiple bitfields to exist at the same time. I ended up with a pretty
|
||||
similar predicate that implemented the "store if not set, intersect if set"
|
||||
behavior in Alloy:
|
||||
|
||||
{{< codelines "Alloy" "dyno-alloy/DynoAlloy.als" 147 150 >}}
|
||||
|
||||
The first line of the predicate represents the second item from the list
|
||||
above: if a scope hasn't been searched before, the new value is just the
|
||||
current filter / bitfield. The second line handles the third item:
|
||||
updating a previously-set filter based on new flags. I defined an
|
||||
additional predicate to help with this:
|
||||
|
||||
{{< codelines "Alloy" "dyno-alloy/DynoAlloy.als" 138 140 >}}
|
||||
|
||||
At first, this predicate doesn't seem like much. However, if you look
|
||||
closely, this single-line predicate uses a feature of Alloy we haven't
|
||||
really seen: its ability to reason about time by dipping into _temporal logic_.
|
||||
Notice that the predicate is written not just in terms of `toSet`, but also
|
||||
`toSet'`. The tick (which I personally read as "prime") indicates that what
|
||||
we're talking about is not the _current_ value of `toSet`, but its value at
|
||||
the _next moment in time_. What this predicate says, then, is that _at the next
|
||||
moment_, the value of `toSet` will be equal to its present value,
|
||||
intersected with `curFilter`. I also had to specify that `toSet'`, the
|
||||
future value of `toSet`, will still be a Bitfield after the step, and would
|
||||
not revert to a `NotSet`.
|
||||
|
||||
{{< todo >}}
|
||||
The rest of the article
|
||||
{{< /todo >}}
|
||||
|
@ -408,6 +650,14 @@ The rest of the article
|
|||
This section is temporary
|
||||
{{< /todo >}}
|
||||
|
||||
|
||||
More abstractly,
|
||||
{{< sidenote "right" "notation-note" "we could denote the claim" >}}
|
||||
This is in no way standard notation; I'm making it up as I go along.
|
||||
{{< /sidenote >}}
|
||||
that some state \\(A\\) is changed to state \\(B\\) by a statement \\(s\\)
|
||||
as follows:
|
||||
|
||||
a small-ish program to illustrate what I mean.
|
||||
|
||||
```Chapel
|
||||
|
|
Loading…
Reference in New Issue
Block a user