1 import Base 2 import ../types/model.bend as T 3 import ../spec/clock.bend as Clock 4 import ./cache.bend as C 5 import ./public.bend as Pub 6 7 # Deterministic model of the host clock loop over an explicit provider-event 8 # list. Only the neutral Clock.ClockEvent/Clock.Error datatypes are imported from the 9 # clock specification; no specification function is used. The adapter runs 10 # this machine through src/host.bend begin/answer (proofs/host_loop.bend). 11 type Outcome<-K: Data, -V: Data> is Data: 12 Returned{cache: C.Cache, answer: Pub.Answer, remaining: List<&2, Clock.ClockEvent>, requests: Nat} 13 Failed{cache: C.Cache, error: Clock.Error, remaining: List<&2, Clock.ClockEvent>, requests: Nat} 14 15 def bump(-K: Data, -V: Data, outcome: Outcome) -> Outcome: 16 match outcome: 17 case Returned{c, answer, events, requests}: Returned{c, answer, events, 1n+requests} 18 case Failed{c, error, events, requests}: Failed{c, error, events, 1n+requests} 19 20 # One step: the provider event answering a Waiting progress either resumes the 21 # machine or stops it with the retained state and the error. drive is its 22 # iteration (drive_advance in END_TO_END.bend); src/host.bend answer uses it. 23 type Step<-K: Data, -V: Data> is Data: 24 Continue{progress: Pub.Progress} 25 Stopped{cache: C.Cache, error: Clock.Error} 26 27 def advance(-K: Data, -V: Data, zero_value: V, pending: Pub.Pending, event: Clock.ClockEvent) -> Step: 28 match event: 29 case Clock.Sample{now}: Continue{Pub.resume(K, V, zero_value, pending, now)} 30 case Clock.InvalidSample{reason}: Stopped{Pub.abandon(K, V, pending), Clock.Invalid{reason}} 31 case Clock.ProviderException{reason}: Stopped{Pub.abandon(K, V, pending), Clock.Thrown{reason}} 32 33 def drive(-K: Data, -V: Data, +zero_value: V, events: List<&2, Clock.ClockEvent>, progress: Pub.Progress) -> Outcome: 34 match events progress: 35 case remaining Pub.Finished{c, answer}: Returned{c, answer, remaining, 0n} 36 case Nil{} Pub.Waiting{pending}: Failed{Pub.abandon(K, V, pending), Clock.Exhausted{}, Nil{}, 1n} 37 case Con{Clock.InvalidSample{reason}, tail} Pub.Waiting{pending}: Failed{Pub.abandon(K, V, pending), Clock.Invalid{reason}, tail, 1n} 38 case Con{Clock.ProviderException{reason}, tail} Pub.Waiting{pending}: Failed{Pub.abandon(K, V, pending), Clock.Thrown{reason}, tail, 1n} 39 case Con{Clock.Sample{now}, tail} Pub.Waiting{pending}: bump(K, V, drive(K, V, zero_value, tail, Pub.resume(K, V, zero_value, pending, now))) 40 41 def execute(~K: Data, ~encode: K -> String, -V: Data, zero_key: K, +zero_value: V, c: C.Cache, events: List<&2, Clock.ClockEvent>, request: Pub.Request) -> Outcome: 42 drive(K, V, zero_value, events, Pub.start(K, V, encode, zero_key, zero_value, c, request)) 43 44 def cache(-K: Data, -V: Data, outcome: Outcome) -> C.Cache: 45 match outcome: 46 case Returned{c, answer, events, requests}: c 47 case Failed{c, error, events, requests}: c 48 49 def remaining(-K: Data, -V: Data, outcome: Outcome) -> List<&2, Clock.ClockEvent>: 50 match outcome: 51 case Returned{c, answer, events, requests}: events 52 case Failed{c, error, events, requests}: events 53 54 def requests(-K: Data, -V: Data, outcome: Outcome) -> Nat: 55 match outcome: 56 case Returned{c, answer, events, count}: count 57 case Failed{c, error, events, count}: count 58 59 # A caller may catch a failed command and continue from the retained state. 60 type Trace<-K: Data, -V: Data> is Data: 61 Ran{cache: C.Cache, remaining: List<&2, Clock.ClockEvent>, outcomes: List<&2, Outcome>, requests: Nat} 62 63 def prepend(-K: Data, -V: Data, +outcome: Outcome, rest: Trace) -> Trace: 64 Ran{c, events, outcomes, count} = rest 65 Ran{c, events, Con{outcome, outcomes}, Nat.add(requests(K, V, outcome), count)} 66 67 def run(~K: Data, ~encode: K -> String, -V: Data, requests: List<&2, Pub.Request>, c: C.Cache, +zero_key: K, +zero_value: V, events: List<&2, Clock.ClockEvent>) -> Trace: 68 match requests: 69 case Nil{}: Ran{c, events, Nil{}, 0n} 70 case Con{request, tail}: 71 +outcome = execute(~K, ~encode, V, zero_key, zero_value, c, events, request) 72 prepend(K, V, outcome, run(~K, ~encode, V, tail, cache(K, V, outcome), zero_key, zero_value, remaining(K, V, outcome)))