Turnstile
A coin unlocks the gate and a push lets one person through: three machines that differ only in a coin while the gate is open, step by step, side by side, and over every short sequence of events.
Every screen below was recorded under CPython. When this page was built, the EML interpreter replayed each session from the same input and printed the same bytes.
About
A coin unlocks the gate and a push lets one person through; a push on a locked gate is refused. Three machines differ only in what a coin does when the gate is already open.
main.eml- the menu, the machines, one machine step by step, all three side by side, and every short sequence of eventsgate.eml- one step of each machine, a run of events with its totals, and the count over every sequence
How each part works:
- As in the corpus case
turnstile-simulator, a state machine driven by events, with running counts beside it. The state is the number of passages paid for and not yet used. - A coin while the gate is open: - classic keeps the coin, and the gate stays open for one person: the coin buys nothing; - credit counts it, so one more person may pass; - refund gives it back.
- On every machine and every sequence, coins paid = people passed + coins lost + passages still owed.
- Every sequence of 1 to 8 events: classic loses a coin in 369 of the 510, and up to 7 in one; the other two never do. Classic loses a coin exactly when a coin comes while the gate is open.
What is checked: menu choices 1 to 5; one of the three machines; events of c and p, in either case, with or without spaces, up to 20. An empty answer cancels.
Sessions: sessions/basic.in gives: - the machines; - c p p c c p p on classic (a coin lost, a push refused) and on credit (all three pass); - c c c p p p p and p c p c p side by side; - every sequence.
sessions/bad-input.in gives: - menu choices 0 and x; - a cancel and turbo for the machine; - events: a cancel, c x p, and 21 coins, then CP CP on refund; - a cancel side by side.
Built on the verified corpus case turnstile-simulator (a two-state machine and its counters, driven by a list of events).
Recorded sessions
What the screen shows while someone uses the program. Each typed line appears after its prompt, the way a terminal shows it.
bad-input
interpreter: byte-equal== Turnstile ==
A coin unlocks the gate and a push lets one person through. Three machines differ only in what a coin does when the gate is already open.
1) machines 2) one machine 3) side by side 4) every sequence 5) quit
choice> 0
Pick a number from 1 to 5.
1) machines 2) one machine 3) side by side 4) every sequence 5) quit
choice> x
Pick a number from 1 to 5.
1) machines 2) one machine 3) side by side 4) every sequence 5) quit
choice> 2
machine (classic, credit or refund)>
Cancelled.
1) machines 2) one machine 3) side by side 4) every sequence 5) quit
choice> 2
machine (classic, credit or refund)> turbo
Type classic, credit or refund.
1) machines 2) one machine 3) side by side 4) every sequence 5) quit
choice> 2
machine (classic, credit or refund)> classic
Events: c for a coin, p for a push, up to 20, such as c p p c c p.
events>
Cancelled.
1) machines 2) one machine 3) side by side 4) every sequence 5) quit
choice> 2
machine (classic, credit or refund)> classic
Events: c for a coin, p for a push, up to 20, such as c p p c c p.
events> c x p
Type only c, p and spaces.
1) machines 2) one machine 3) side by side 4) every sequence 5) quit
choice> 2
machine (classic, credit or refund)> classic
Events: c for a coin, p for a push, up to 20, such as c p p c c p.
events> c c c c c c c c c c c c c c c c c c c c c
Up to 20 events.
1) machines 2) one machine 3) side by side 4) every sequence 5) quit
choice> 2
machine (classic, credit or refund)> refund
Events: c for a coin, p for a push, up to 20, such as c p p c c p.
events> CP CP
refund:
1 coin locked -> open coin taken
2 push open -> locked one person passes
3 coin locked -> open coin taken
4 push open -> locked one person passes
paid 2, passed 2, refused 0, given back 0, lost 0, still owed 0.
1) machines 2) one machine 3) side by side 4) every sequence 5) quit
choice> 3
Events: c for a coin, p for a push, up to 20, such as c p p c c p.
events>
Cancelled.
1) machines 2) one machine 3) side by side 4) every sequence 5) quit
choice> 5
Bye.
What was typed (21 lines)
0
x
2
2
turbo
2
classic
2
classic
c x p
2
classic
c c c c c c c c c c c c c c c c c c c c c
2
refund
CP CP
3
5
basic
interpreter: byte-equal== Turnstile ==
A coin unlocks the gate and a push lets one person through. Three machines differ only in what a coin does when the gate is already open.
1) machines 2) one machine 3) side by side 4) every sequence 5) quit
choice> 1
Every machine: a coin opens a locked gate; a push through an open gate lets one person pass and locks it once nothing more is paid; a push on a locked gate is refused.
A coin while the gate is already open:
classic keeps the coin, and the gate stays open for one person
credit counts it: one more person may pass
refund gives it back
1) machines 2) one machine 3) side by side 4) every sequence 5) quit
choice> 2
machine (classic, credit or refund)> classic
Events: c for a coin, p for a push, up to 20, such as c p p c c p.
events> c p p c c p p
classic:
1 coin locked -> open coin taken
2 push open -> locked one person passes
3 push locked refused: pay first
4 coin locked -> open coin taken
5 coin open coin kept, but the gate was already open: it buys nothing
6 push open -> locked one person passes
7 push locked refused: pay first
paid 3, passed 2, refused 2, given back 0, lost 1, still owed 0.
1) machines 2) one machine 3) side by side 4) every sequence 5) quit
choice> 2
machine (classic, credit or refund)> credit
Events: c for a coin, p for a push, up to 20, such as c p p c c p.
events> c p p c c p p
credit:
1 coin locked -> open coin taken
2 push open -> locked one person passes
3 push locked refused: pay first
4 coin locked -> open coin taken
5 coin open -> open, 2 paid coin taken
6 push open, 2 paid -> open one person passes
7 push open -> locked one person passes
paid 3, passed 3, refused 1, given back 0, lost 0, still owed 0.
1) machines 2) one machine 3) side by side 4) every sequence 5) quit
choice> 3
Events: c for a coin, p for a push, up to 20, such as c p p c c p.
events> c c c p p p p
classic paid 3, passed 1, refused 3, given back 0, lost 2, still owed 0
credit paid 3, passed 3, refused 1, given back 0, lost 0, still owed 0
refund paid 1, passed 1, refused 3, given back 2, lost 0, still owed 0
Coins paid = people passed + coins lost + passages still owed, on every machine.
1) machines 2) one machine 3) side by side 4) every sequence 5) quit
choice> 3
Events: c for a coin, p for a push, up to 20, such as c p p c c p.
events> p c p c p
classic paid 2, passed 2, refused 1, given back 0, lost 0, still owed 0
credit paid 2, passed 2, refused 1, given back 0, lost 0, still owed 0
refund paid 2, passed 2, refused 1, given back 0, lost 0, still owed 0
Coins paid = people passed + coins lost + passages still owed, on every machine.
1) machines 2) one machine 3) side by side 4) every sequence 5) quit
choice> 4
Every sequence of 1 to 8 events, 510 in all:
classic loses a coin in 369 of the 510, and up to 7 coins in one.
credit never loses a coin.
refund never loses a coin.
Only classic loses coins, and exactly when a coin comes while the gate is open.
1) machines 2) one machine 3) side by side 4) every sequence 5) quit
choice> 5
Bye.
What was typed (13 lines)
1
2
classic
c p p c c p p
2
credit
c p p c c p p
3
c c c p p p p
3
p c p c p
4
5
Modules
The program as written, entry module first. Each module transpiles to its own Python file, which is what eml project run executes.
main.eml(entry)
eml# P122 turnstile: three machines, a list of coins and pushes run through
# one of them step by step or through all three side by side, and every
# short sequence of events counted.
import gate
def plain(s):
0 => i
len(s) => j
while i < j and s[i] == " ":
i + 1 => i
while j > i and s[j - 1] == " ":
j - 1 => j
return s[i:j]
def pad(s, n):
s => out
while len(out) < n:
out + " " => out
return out
def lpad(s, n):
s => out
while len(out) < n:
" " + out => out
return out
def state_text(owed):
if owed == 0:
return "locked"
if owed == 1:
return "open"
return "open, " + str(owed) + " paid"
def show():
"Every machine: a coin opens a locked gate; a push through an open gate lets one person pass and locks it once nothing more is paid; a push on a locked gate is refused." ^0
"A coin while the gate is already open:" ^0
" classic keeps the coin, and the gate stays open for one person" ^0
" credit counts it: one more person may pass" ^0
" refund gives it back" ^0
def ask_events():
("Events: c for a coin, p for a push, up to 20, such as c p p c c p.") ^0
plain(input("events> ")) => t
if t == "":
"Cancelled." ^0
return []
[] => out
for c in t:
if c == "c" or c == "C":
out + ["coin"] => out
elif c == "p" or c == "P":
out + ["push"] => out
elif c != " ":
"Type only c, p and spaces." ^0
return []
if len(out) > 20:
"Up to 20 events." ^0
return []
return out
def what_text(w):
if w == "taken":
return "coin taken"
if w == "refunded":
return "coin given back: the gate is already open"
if w == "passes":
return "one person passes"
if w == "refused":
return "refused: pay first"
return "coin kept, but the gate was already open: it buys nothing"
def totals_text(t):
return "paid " + str(t["paid"]) + ", passed " + str(t["passes"]) + ", refused " + str(t["refused"]) + ", given back " + str(t["refunded"]) + ", lost " + str(t["lost"]) + ", still owed " + str(t["owed"])
def ask_machine():
plain(input("machine (classic, credit or refund)> ")) => m
if m == "":
"Cancelled." ^0
return ""
for x in gate.machines():
if x == m:
return m
"Type classic, credit or refund." ^0
return ""
def one(machine):
ask_events() => evs
if len(evs) == 0:
return
gate.run(machine, evs) => r
(machine + ":") ^0
for k in [0:len(evs) - 1]:
r[0][k] => e
state_text(e[1]) => a
state_text(e[2]) => b
if a == b:
(" " + lpad(str(k + 1), 2) + " " + pad(e[0], 5) + pad(a, 22) + what_text(e[3])) ^0
else:
(" " + lpad(str(k + 1), 2) + " " + pad(e[0], 5) + pad(a + " -> " + b, 22) + what_text(e[3])) ^0
(" " + totals_text(r[1]) + ".") ^0
def side():
ask_events() => evs
if len(evs) == 0:
return
for m in gate.machines():
(" " + pad(m, 8) + totals_text(gate.run(m, evs)[1])) ^0
"Coins paid = people passed + coins lost + passages still owed, on every machine." ^0
def every():
"Every sequence of 1 to 8 events, 510 in all:" ^0
for m in gate.machines():
gate.census(m, 8) => c
if c[1] == 0:
(" " + pad(m, 8) + "never loses a coin.") ^0
else:
(" " + pad(m, 8) + "loses a coin in " + str(c[1]) + " of the " + str(c[0]) + ", and up to " + str(c[2]) + " coins in one.") ^0
"Only classic loses coins, and exactly when a coin comes while the gate is open." ^0
"== Turnstile ==" ^0
"A coin unlocks the gate and a push lets one person through. Three machines differ only in what a coin does when the gate is already open." ^0
True => running
while running:
"" ^0
"1) machines 2) one machine 3) side by side 4) every sequence 5) quit" ^0
plain(input("choice> ")) => choice
if choice == "1":
show()
elif choice == "2":
ask_machine() => m
if m != "":
one(m)
elif choice == "3":
side()
elif choice == "4":
every()
elif choice == "5":
False => running
else:
"Pick a number from 1 to 5." ^0
"Bye." ^0
Python projection (main.py)
import gate
def plain(s):
i = 0
j = len(s)
while i < j and s[i] == " ":
i = i + 1
while j > i and s[j - 1] == " ":
j = j - 1
return s[i:j]
def pad(s, n):
out = s
while len(out) < n:
out = out + " "
return out
def lpad(s, n):
out = s
while len(out) < n:
out = " " + out
return out
def state_text(owed):
if owed == 0:
return "locked"
if owed == 1:
return "open"
return "open, " + str(owed) + " paid"
def show():
print("Every machine: a coin opens a locked gate; a push through an open gate lets one person pass and locks it once nothing more is paid; a push on a locked gate is refused.")
print("A coin while the gate is already open:")
print(" classic keeps the coin, and the gate stays open for one person")
print(" credit counts it: one more person may pass")
print(" refund gives it back")
def ask_events():
print("Events: c for a coin, p for a push, up to 20, such as c p p c c p.")
t = plain(input("events> "))
if t == "":
print("Cancelled.")
return []
out = []
for c in t:
if c == "c" or c == "C":
out = out + ["coin"]
elif c == "p" or c == "P":
out = out + ["push"]
elif c != " ":
print("Type only c, p and spaces.")
return []
if len(out) > 20:
print("Up to 20 events.")
return []
return out
def what_text(w):
if w == "taken":
return "coin taken"
if w == "refunded":
return "coin given back: the gate is already open"
if w == "passes":
return "one person passes"
if w == "refused":
return "refused: pay first"
return "coin kept, but the gate was already open: it buys nothing"
def totals_text(t):
return "paid " + str(t["paid"]) + ", passed " + str(t["passes"]) + ", refused " + str(t["refused"]) + ", given back " + str(t["refunded"]) + ", lost " + str(t["lost"]) + ", still owed " + str(t["owed"])
def ask_machine():
m = plain(input("machine (classic, credit or refund)> "))
if m == "":
print("Cancelled.")
return ""
for x in gate.machines():
if x == m:
return m
print("Type classic, credit or refund.")
return ""
def one(machine):
evs = ask_events()
if len(evs) == 0:
return
r = gate.run(machine, evs)
print(machine + ":")
for k in range(0, len(evs)):
e = r[0][k]
a = state_text(e[1])
b = state_text(e[2])
if a == b:
print(" " + lpad(str(k + 1), 2) + " " + pad(e[0], 5) + pad(a, 22) + what_text(e[3]))
else:
print(" " + lpad(str(k + 1), 2) + " " + pad(e[0], 5) + pad(a + " -> " + b, 22) + what_text(e[3]))
print(" " + totals_text(r[1]) + ".")
def side():
evs = ask_events()
if len(evs) == 0:
return
for m in gate.machines():
print(" " + pad(m, 8) + totals_text(gate.run(m, evs)[1]))
print("Coins paid = people passed + coins lost + passages still owed, on every machine.")
def every():
print("Every sequence of 1 to 8 events, 510 in all:")
for m in gate.machines():
c = gate.census(m, 8)
if c[1] == 0:
print(" " + pad(m, 8) + "never loses a coin.")
else:
print(" " + pad(m, 8) + "loses a coin in " + str(c[1]) + " of the " + str(c[0]) + ", and up to " + str(c[2]) + " coins in one.")
print("Only classic loses coins, and exactly when a coin comes while the gate is open.")
print("== Turnstile ==")
print("A coin unlocks the gate and a push lets one person through. Three machines differ only in what a coin does when the gate is already open.")
running = True
while running:
print("")
print("1) machines 2) one machine 3) side by side 4) every sequence 5) quit")
choice = plain(input("choice> "))
if choice == "1":
show()
elif choice == "2":
m = ask_machine()
if m != "":
one(m)
elif choice == "3":
side()
elif choice == "4":
every()
elif choice == "5":
running = False
else:
print("Pick a number from 1 to 5.")
print("Bye.")
gate.eml
eml# P122 turnstile - a coin unlocks it, a push lets one person through and
# locks it again; a push while locked is refused. As in the corpus case
# turnstile-simulator, a two-state machine driven by events. Three machines
# differ only in a coin that arrives while the gate is already open:
# classic keeps the coin and stays open: one passage for two coins;
# credit counts it: each coin is one passage, however they arrive;
# refund gives it back.
# The state is the number of passages paid for and not yet used (classic
# and refund never hold more than one).
def machines():
return ["classic", "credit", "refund"]
def step(machine, owed, ev):
# [passages owed after, what happened]: what is "taken", "refunded",
# "passes", "refused" or "lost" (a coin taken that buys nothing)
if ev == "coin":
if owed == 0 or machine == "credit":
return [owed + 1, "taken"]
if machine == "refund":
return [owed, "refunded"]
return [owed, "lost"]
if owed > 0:
return [owed - 1, "passes"]
return [owed, "refused"]
def run(machine, events):
# [trace of [event, owed before, owed after, what], totals {name: n}]
0 => owed
[] => trace
{"taken": 0, "refunded": 0, "passes": 0, "refused": 0, "lost": 0} => tot
for ev in events:
step(machine, owed, ev) => r
trace + [[ev, owed, r[0], r[1]]] => trace
r[0] => owed
tot[r[1]] + 1 => tot[r[1]]
tot["taken"] + tot["lost"] => tot["paid"]
owed => tot["owed"]
return [trace, tot]
def sequences(n):
# every sequence of n events
[[]] => out
for k in [1:n]:
[] => more
for s in out:
more + [s + ["coin"]] => more
more + [s + ["push"]] => more
more => out
return out
def census(machine, n):
# over every sequence of 1 to n events: [sequences, sequences losing a
# coin, most coins lost in one, most passages refused in one]
0 => count
0 => losing
0 => most
0 => refused
for k in [1:n]:
for s in sequences(k):
run(machine, s)[1] => t
count + 1 => count
if t["lost"] > 0:
losing + 1 => losing
if t["lost"] > most:
t["lost"] => most
if t["refused"] > refused:
t["refused"] => refused
return [count, losing, most, refused]
Python projection (gate.py)
def machines():
return ["classic", "credit", "refund"]
def step(machine, owed, ev):
if ev == "coin":
if owed == 0 or machine == "credit":
return [owed + 1, "taken"]
if machine == "refund":
return [owed, "refunded"]
return [owed, "lost"]
if owed > 0:
return [owed - 1, "passes"]
return [owed, "refused"]
def run(machine, events):
owed = 0
trace = []
tot = {"taken": 0, "refunded": 0, "passes": 0, "refused": 0, "lost": 0}
for ev in events:
r = step(machine, owed, ev)
trace = trace + [[ev, owed, r[0], r[1]]]
owed = r[0]
tot[r[1]] = tot[r[1]] + 1
tot["paid"] = tot["taken"] + tot["lost"]
tot["owed"] = owed
return [trace, tot]
def sequences(n):
out = [[]]
for k in range(1, n+1):
more = []
for s in out:
more = more + [s + ["coin"]]
more = more + [s + ["push"]]
out = more
return out
def census(machine, n):
count = 0
losing = 0
most = 0
refused = 0
for k in range(1, n+1):
for s in sequences(k):
t = run(machine, s)[1]
count = count + 1
if t["lost"] > 0:
losing = losing + 1
if t["lost"] > most:
most = t["lost"]
if t["refused"] > refused:
refused = t["refused"]
return [count, losing, most, refused]