2016-12-27 08:10:47 -08:00

218 lines
6.4 KiB
Alloy

module examples/systems/views
/*
* Model of views in object-oriented programming.
*
* Two object references, called the view and the backing,
* are related by a view mechanism when changes to the
* backing are automatically propagated to the view. Note
* that the state of a view need not be a projection of the
* state of the backing; the keySet method of Map, for
* example, produces two view relationships, and for the
* one in which the map is modified by changes to the key
* set, the value of the new map cannot be determined from
* the key set. Note that in the iterator view mechanism,
* the iterator is by this definition the backing object,
* since changes are propagated from iterator to collection
* and not vice versa. Oddly, a reference may be a view of
* more than one backing: there can be two iterators on the
* same collection, eg. A reference cannot be a view under
* more than one view type.
*
* A reference is made dirty when it is a backing for a view
* with which it is no longer related by the view invariant.
* This usually happens when a view is modified, either
* directly or via another backing. For example, changing a
* collection directly when it has an iterator invalidates
* it, as does changing the collection through one iterator
* when there are others.
*
* More work is needed if we want to model more closely the
* failure of an iterator when its collection is invalidated.
*
* As a terminological convention, when there are two
* complementary view relationships, we will give them types
* t and t'. For example, KeySetView propagates from map to
* set, and KeySetView' propagates from set to map.
*
* author: Daniel Jackson
*/
open util/ordering[State] as so
open util/relation as rel
sig Ref {}
sig Object {}
-- t->b->v in views when v is view of type t of backing b
-- dirty contains refs that have been invalidated
sig State {
refs: set Ref,
obj: refs -> one Object,
views: ViewType -> refs -> refs,
dirty: set refs
-- , anyviews: Ref -> Ref -- for visualization
}
-- {anyviews = ViewType.views}
sig Map extends Object {
keys: set Ref,
map: keys -> one Ref
}{all s: State | keys + Ref.map in s.refs}
sig MapRef extends Ref {}
fact {State.obj[MapRef] in Map}
sig Iterator extends Object {
left, done: set Ref,
lastRef: lone done
}{all s: State | done + left + lastRef in s.refs}
sig IteratorRef extends Ref {}
fact {State.obj[IteratorRef] in Iterator}
sig Set extends Object {
elts: set Ref
}{all s: State | elts in s.refs}
sig SetRef extends Ref {}
fact {State.obj[SetRef] in Set}
abstract sig ViewType {}
one sig KeySetView, KeySetView', IteratorView extends ViewType {}
fact ViewTypes {
State.views[KeySetView] in MapRef -> SetRef
State.views[KeySetView'] in SetRef -> MapRef
State.views[IteratorView] in IteratorRef -> SetRef
all s: State | s.views[KeySetView] = ~(s.views[KeySetView'])
}
/**
* mods is refs modified directly or by view mechanism
* doesn't handle possibility of modifying an object and its view at once?
* should we limit frame conds to non-dirty refs?
*/
pred modifies [pre, post: State, rs: set Ref] {
let vr = pre.views[ViewType], mods = rs.*vr {
all r: pre.refs - mods | pre.obj[r] = post.obj[r]
all b: mods, v: pre.refs, t: ViewType |
b->v in pre.views[t] => viewFrame [t, pre.obj[v], post.obj[v], post.obj[b]]
post.dirty = pre.dirty +
{b: pre.refs | some v: Ref, t: ViewType |
b->v in pre.views[t] && !viewFrame [t, pre.obj[v], post.obj[v], post.obj[b]]
}
}
}
pred allocates [pre, post: State, rs: set Ref] {
no rs & pre.refs
post.refs = pre.refs + rs
}
/**
* models frame condition that limits change to view object from v to v' when backing object changes to b'
*/
pred viewFrame [t: ViewType, v, v', b': Object] {
t in KeySetView => v'.elts = dom [b'.map]
t in KeySetView' => b'.elts = dom [v'.map]
t in KeySetView' => (b'.elts) <: (v.map) = (b'.elts) <: (v'.map)
t in IteratorView => v'.elts = b'.left + b'.done
}
pred MapRef.keySet [pre, post: State, setRefs: SetRef] {
post.obj[setRefs].elts = dom [pre.obj[this].map]
modifies [pre, post, none]
allocates [pre, post, setRefs]
post.views = pre.views + KeySetView->this->setRefs + KeySetView'->setRefs->this
}
pred MapRef.put [pre, post: State, k, v: Ref] {
post.obj[this].map = pre.obj[this].map ++ k->v
modifies [pre, post, this]
allocates [pre, post, none]
post.views = pre.views
}
pred SetRef.iterator [pre, post: State, iterRef: IteratorRef] {
let i = post.obj[iterRef] {
i.left = pre.obj[this].elts
no i.done + i.lastRef
}
modifies [pre,post,none]
allocates [pre, post, iterRef]
post.views = pre.views + IteratorView->iterRef->this
}
pred IteratorRef.remove [pre, post: State] {
let i = pre.obj[this], i' = post.obj[this] {
i'.left = i.left
i'.done = i.done - i.lastRef
no i'.lastRef
}
modifies [pre,post,this]
allocates [pre, post, none]
pre.views = post.views
}
pred IteratorRef.next [pre, post: State, ref: Ref] {
let i = pre.obj[this], i' = post.obj[this] {
ref in i.left
i'.left = i.left - ref
i'.done = i.done + ref
i'.lastRef = ref
}
modifies [pre, post, this]
allocates [pre, post, none]
pre.views = post.views
}
pred IteratorRef.hasNext [s: State] {
some s.obj[this].left
}
assert zippishOK {
all
ks, vs: SetRef,
m: MapRef,
ki, vi: IteratorRef,
k, v: Ref |
let s0=so/first,
s1=so/next[s0],
s2=so/next[s1],
s3=so/next[s2],
s4=so/next[s3],
s5=so/next[s4],
s6=so/next[s5],
s7=so/next[s6] |
({
precondition [s0, ks, vs, m]
no s0.dirty
ks.iterator [s0, s1, ki]
vs.iterator [s1, s2, vi]
ki.hasNext [s2]
vi.hasNext [s2]
ki.this/next [s2, s3, k]
vi.this/next [s3, s4, v]
m.put [s4, s5, k, v]
ki.remove [s5, s6]
vi.remove [s6, s7]
} => no State.dirty)
}
pred precondition [pre: State, ks, vs, m: Ref] {
// all these conditions and other errors discovered in scope of 6 but 8,3
// in initial state, must have view invariants hold
(all t: ViewType, b, v: pre.refs |
b->v in pre.views[t] => viewFrame [t, pre.obj[v], pre.obj[v], pre.obj[b]])
// sets are not aliases
-- ks != vs
// sets are not views of map
-- no (ks+vs)->m & ViewType.pre.views
// no iterator currently on either set
-- no Ref->(ks+vs) & ViewType.pre.views
}
check zippishOK for 6 but 8 State, 3 ViewType expect 1
/**
* experiment with controlling heap size
*/
fact {all s: State | #s.obj < 5}