Input sanitizer
Cleanup steps for typed text, which of them are safe to repeat, and pipelines of safe steps that still change on a second pass because a later step hands back work an earlier one would still do.
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
Cleanup steps for text people type, which of them are safe to repeat, and which pipelines built from safe steps are not.
main.eml- the menu, the steps measured on the samples, the samples, a pipeline run once, twice and three times, and every ordered pair of the safe stepsclean.eml- the six steps, the samples, running a pipeline, whether a second pass changes it, and the first step that would still change a text
How each part works:
- The steps: trim, squeeze runs of spaces, delete every < and >, keep the first 16 characters, escape every &, and escape only a bare &.
- As in the corpus case
idempotence-witness, a step is safe to retry when a second application changes nothing. Escaping every & is not:Tom & JerrybecomesTom & Jerryand thenTom &amp; Jerry. Escaping only an & that does not already begin&is. - As in the corpus case
idempotence-does-not-compose, a queue that redelivers replays the whole pipeline, and a pipeline of safe steps can still change on the second pass. Trim then unbracket turnsx <intox, and the second pass trims the space it exposed; trim then cut leaves the spaces the cut ends on. 4 of the 20 ordered pairs change on the samples. - The reason is measured beside it: a pair changes when the second step hands back text the first step would still change. Whenever the first step has no work left, the pair is safe. The converse does not hold: escape a bare & then cut settles on
Fresh breads & jam, yet the escape would still changeFresh breads &am- the cut takes its change back every time, and the text ends in a broken&am.
What is checked: menu choices 1 to 5; 1 to 6 steps, each a digit from 1 to 6; a text of up to 40 characters, or a single digit 1 to 9 for a sample. An empty answer cancels.
Sessions: sessions/basic.in gives: - the steps and the samples; - trim then unbracket on x <, and the other order; escape every & on Tom & Jerry; trim then cut on sample 8, and the other order; - escape a bare & on AT&T, and then cut on sample 9; trim, squeeze and unbracket on a text of its own; every pair.
sessions/bad-input.in gives: - menu choices 0 and x; - steps: a cancel, 17, seven digits and a; - texts: a cancel and a text of 41 characters; the digit 0 run as its own text, and escape every & on A & B.
Built on the verified corpus cases idempotence-witness (escaping as the step that adds on a retry, and its guarded form) and idempotence-does-not-compose (pairs of idempotent steps that are not idempotent, and the mechanism measured).
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== Input sanitizer ==
Cleanup steps for typed text: which are safe to repeat, and which pipelines of safe steps are not.
1) steps 2) samples 3) run a pipeline 4) every pair 5) quit
choice> 0
Pick a number from 1 to 5.
1) steps 2) samples 3) run a pipeline 4) every pair 5) quit
choice> x
Pick a number from 1 to 5.
1) steps 2) samples 3) run a pipeline 4) every pair 5) quit
choice> 3
steps, in order (digits 1 to 6, e.g. 13)>
Cancelled.
1) steps 2) samples 3) run a pipeline 4) every pair 5) quit
choice> 3
steps, in order (digits 1 to 6, e.g. 13)> 17
Type 1 to 6 steps, each a digit from 1 to 6.
1) steps 2) samples 3) run a pipeline 4) every pair 5) quit
choice> 3
steps, in order (digits 1 to 6, e.g. 13)> 1234561
Type 1 to 6 steps, each a digit from 1 to 6.
1) steps 2) samples 3) run a pipeline 4) every pair 5) quit
choice> 3
steps, in order (digits 1 to 6, e.g. 13)> a
Type 1 to 6 steps, each a digit from 1 to 6.
1) steps 2) samples 3) run a pipeline 4) every pair 5) quit
choice> 3
steps, in order (digits 1 to 6, e.g. 13)> 1
text (a sample 1 to 9, or your own)>
Cancelled.
1) steps 2) samples 3) run a pipeline 4) every pair 5) quit
choice> 3
steps, in order (digits 1 to 6, e.g. 13)> 1
text (a sample 1 to 9, or your own)> this text is longer than forty characters!
Type a sample number or a text of up to 40 characters.
1) steps 2) samples 3) run a pipeline 4) every pair 5) quit
choice> 3
steps, in order (digits 1 to 6, e.g. 13)> 2
text (a sample 1 to 9, or your own)> 0
Pipeline: squeeze.
text: '0'
one pass: '0'
two passes: '0'
three passes: '0'
A second pass changes nothing: replayed, this pipeline gives the same text.
1) steps 2) samples 3) run a pipeline 4) every pair 5) quit
choice> 3
steps, in order (digits 1 to 6, e.g. 13)> 5
text (a sample 1 to 9, or your own)> A & B
Pipeline: escape every &.
text: 'A & B'
one pass: 'A & B'
two passes: 'A &amp; B'
three passes: 'A &amp;amp; B'
The second pass changed it: replayed, this pipeline does not give the same text.
Escape every & changes the text again on every pass: it is not safe to repeat even alone.
1) steps 2) samples 3) run a pipeline 4) every pair 5) quit
choice> 5
Bye.
What was typed (23 lines)
0
x
3
3
17
3
1234561
3
a
3
1
3
1
this text is longer than forty characters!
3
2
0
3
5
A & B
5
basic
interpreter: byte-equal== Input sanitizer ==
Cleanup steps for typed text: which are safe to repeat, and which pipelines of safe steps are not.
1) steps 2) samples 3) run a pipeline 4) every pair 5) quit
choice> 1
The steps, each run twice on every sample:
1 trim take the spaces off both ends
safe to repeat on all 9 samples
2 squeeze make each run of spaces one space
safe to repeat on all 9 samples
3 unbracket delete every < and >
safe to repeat on all 9 samples
4 cut keep the first 16 characters
safe to repeat on all 9 samples
5 escape every & write each & as &
changes again on 4 of 9 samples, as 'Tom & Jerry' -> 'Tom & Jerry' -> 'Tom &amp; Jerry'
6 escape a bare & write a & as & unless it already begins &
safe to repeat on all 9 samples
1) steps 2) samples 3) run a pipeline 4) every pair 5) quit
choice> 2
The samples:
1 ' Hello world '
2 'Tom & Jerry'
3 'a < b > c'
4 'x <'
5 '<b>Bold</b> move'
6 'Salt & pepper, please'
7 'AT&T'
8 'one two three four'
9 'Fresh breads & jam'
1) steps 2) samples 3) run a pipeline 4) every pair 5) quit
choice> 3
steps, in order (digits 1 to 6, e.g. 13)> 13
text (a sample 1 to 9, or your own)> 4
Pipeline: trim, then unbracket.
text: 'x <'
one pass: 'x '
two passes: 'x'
three passes: 'x'
The second pass changed it: replayed, this pipeline does not give the same text.
After one pass, trim would still change the text: a step after it handed back work it had already done.
1) steps 2) samples 3) run a pipeline 4) every pair 5) quit
choice> 3
steps, in order (digits 1 to 6, e.g. 13)> 31
text (a sample 1 to 9, or your own)> 4
Pipeline: unbracket, then trim.
text: 'x <'
one pass: 'x'
two passes: 'x'
three passes: 'x'
A second pass changes nothing: replayed, this pipeline gives the same text.
1) steps 2) samples 3) run a pipeline 4) every pair 5) quit
choice> 3
steps, in order (digits 1 to 6, e.g. 13)> 5
text (a sample 1 to 9, or your own)> 2
Pipeline: escape every &.
text: 'Tom & Jerry'
one pass: 'Tom & Jerry'
two passes: 'Tom &amp; Jerry'
three passes: 'Tom &amp;amp; Jerry'
The second pass changed it: replayed, this pipeline does not give the same text.
Escape every & changes the text again on every pass: it is not safe to repeat even alone.
1) steps 2) samples 3) run a pipeline 4) every pair 5) quit
choice> 3
steps, in order (digits 1 to 6, e.g. 13)> 14
text (a sample 1 to 9, or your own)> 8
Pipeline: trim, then cut.
text: 'one two three four'
one pass: 'one two three '
two passes: 'one two three'
three passes: 'one two three'
The second pass changed it: replayed, this pipeline does not give the same text.
After one pass, trim would still change the text: a step after it handed back work it had already done.
1) steps 2) samples 3) run a pipeline 4) every pair 5) quit
choice> 3
steps, in order (digits 1 to 6, e.g. 13)> 41
text (a sample 1 to 9, or your own)> 8
Pipeline: cut, then trim.
text: 'one two three four'
one pass: 'one two three'
two passes: 'one two three'
three passes: 'one two three'
A second pass changes nothing: replayed, this pipeline gives the same text.
1) steps 2) samples 3) run a pipeline 4) every pair 5) quit
choice> 3
steps, in order (digits 1 to 6, e.g. 13)> 6
text (a sample 1 to 9, or your own)> 7
Pipeline: escape a bare &.
text: 'AT&T'
one pass: 'AT&T'
two passes: 'AT&T'
three passes: 'AT&T'
A second pass changes nothing: replayed, this pipeline gives the same text.
1) steps 2) samples 3) run a pipeline 4) every pair 5) quit
choice> 3
steps, in order (digits 1 to 6, e.g. 13)> 64
text (a sample 1 to 9, or your own)> 9
Pipeline: escape a bare &, then cut.
text: 'Fresh breads & jam'
one pass: 'Fresh breads &am'
two passes: 'Fresh breads &am'
three passes: 'Fresh breads &am'
A second pass changes nothing: replayed, this pipeline gives the same text.
Yet escape a bare & would still change 'Fresh breads &am': a step after it takes the change back every time.
1) steps 2) samples 3) run a pipeline 4) every pair 5) quit
choice> 3
steps, in order (digits 1 to 6, e.g. 13)> 123
text (a sample 1 to 9, or your own)> <i>Fish</i> & chips
Pipeline: trim, then squeeze, then unbracket.
text: ' <i>Fish</i> & chips '
one pass: 'iFish/i & chips'
two passes: 'iFish/i & chips'
three passes: 'iFish/i & chips'
A second pass changes nothing: replayed, this pipeline gives the same text.
1) steps 2) samples 3) run a pipeline 4) every pair 5) quit
choice> 4
Every ordered pair of the safe steps, run twice on every sample:
pair second pass first step has work left
trim, then squeeze same none
trim, then unbracket changes on 1 of 9 on 1 of 9
trim, then cut changes on 1 of 9 on 1 of 9
trim, then escape a bare & same none
squeeze, then trim same none
squeeze, then unbracket changes on 1 of 9 on 1 of 9
squeeze, then cut same none
squeeze, then escape a bare & same none
unbracket, then trim same none
unbracket, then squeeze same none
unbracket, then cut same none
unbracket, then escape a bare & same none
cut, then trim same none
cut, then squeeze same none
cut, then unbracket same none
cut, then escape a bare & changes on 2 of 9 on 2 of 9
escape a bare &, then trim same none
escape a bare &, then squeeze same none
escape a bare &, then unbracket same none
escape a bare &, then cut same on 1 of 9
Pairs of safe steps that change on a second pass: 4 of 20.
Pairs where the second pass changes exactly when the first step has work left: 19 of 20, on these samples.
A pair stays safe while its second step never hands back text its first step would still change.
The converse does not hold: with escape a bare &, then cut, a second pass changes nothing, yet escape a bare & would still change 'Fresh breads &am' - cut takes the change back every time.
1) steps 2) samples 3) run a pipeline 4) every pair 5) quit
choice> 5
Bye.
What was typed (28 lines)
1
2
3
13
4
3
31
4
3
5
2
3
14
8
3
41
8
3
6
7
3
64
9
3
123
<i>Fish</i> & chips
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# P142 input sanitizer: the cleanup steps and whether each is safe to
# repeat, the sample texts, a pipeline run once, twice and three times, and
# every ordered pair of the safe steps measured on the samples.
import clean
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 q(s):
return "'" + s + "'"
def of_samples(n):
return str(n) + " of " + str(len(clean.samples()))
def show_steps():
"The steps, each run twice on every sample:" ^0
1 => k
for name in clean.names():
(" " + str(k) + " " + pad(name, 17) + clean.what()[k - 1]) ^0
0 => bad
"" => witness
for x in clean.samples():
if not clean.settles([k], x):
bad + 1 => bad
if witness == "":
clean.step(k, x) => once
q(x) + " -> " + q(once) + " -> " + q(clean.step(k, once)) => witness
if bad == 0:
(" safe to repeat on all " + str(len(clean.samples())) + " samples") ^0
else:
(" changes again on " + of_samples(bad) + " samples, as " + witness) ^0
k + 1 => k
def show_samples():
"The samples:" ^0
1 => k
for x in clean.samples():
(" " + str(k) + " " + q(x)) ^0
k + 1 => k
def step_list(steps):
"" => out
0 => i
for k in steps:
if i == 0:
clean.names()[k - 1] => out
else:
out + ", then " + clean.names()[k - 1] => out
i + 1 => i
return out
def read_steps():
# the steps as a list of numbers, [] for a cancel, [0] when not valid
plain(input("steps, in order (digits 1 to 6, e.g. 13)> ")) => t
if t == "":
return []
if len(t) > 6:
return [0]
[] => steps
for c in t:
if c < "1" or c > "6":
return [0]
steps + [int(c)] => steps
return steps
def run_pipeline():
read_steps() => steps
if len(steps) == 0:
"Cancelled." ^0
return
if steps[0] == 0:
"Type 1 to 6 steps, each a digit from 1 to 6." ^0
return
input("text (a sample 1 to 9, or your own)> ") => t
if t == "":
"Cancelled." ^0
return
if len(t) == 1 and t >= "1" and t <= "9":
clean.samples()[int(t) - 1] => x
elif len(t) > 40:
"Type a sample number or a text of up to 40 characters." ^0
return
else:
t => x
clean.run(steps, x) => once
clean.run(steps, once) => twice
clean.run(steps, twice) => thrice
("Pipeline: " + step_list(steps) + ".") ^0
(" text: " + q(x)) ^0
(" one pass: " + q(once)) ^0
(" two passes: " + q(twice)) ^0
(" three passes: " + q(thrice)) ^0
if twice == once:
"A second pass changes nothing: replayed, this pipeline gives the same text." ^0
clean.work_left(steps, once) => w
if w >= 0:
("Yet " + clean.names()[steps[w] - 1] + " would still change " + q(once) + ": a step after it takes the change back every time.") ^0
else:
"The second pass changed it: replayed, this pipeline does not give the same text." ^0
clean.work_left(steps, once) => w
steps[w] => k
if k == 5:
"Escape every & changes the text again on every pass: it is not safe to repeat even alone." ^0
else:
("After one pass, " + clean.names()[k - 1] + " would still change the text: a step after it handed back work it had already done.") ^0
def pairs():
[1, 2, 3, 4, 6] => safe
"Every ordered pair of the safe steps, run twice on every sample:" ^0
(" " + pad("pair", 34) + pad("second pass", 20) + "first step has work left") ^0
0 => broken
0 => agree
0 => seen
[] => undone
for p in safe:
for r in safe:
if p != r:
0 => bad
0 => left
for x in clean.samples():
if not clean.settles([p, r], x):
bad + 1 => bad
clean.run([p, r], x) => h
if clean.step(p, h) != h:
left + 1 => left
if clean.settles([p, r], x) and len(undone) == 0:
[p, r, h] => undone
if bad == 0:
"same" => verdict
else:
"changes on " + of_samples(bad) => verdict
broken + 1 => broken
if left == 0:
"none" => lw
else:
"on " + of_samples(left) => lw
(" " + pad(step_list([p, r]), 34) + pad(verdict, 20) + lw) ^0
if (bad == 0) == (left == 0):
agree + 1 => agree
seen + 1 => seen
("Pairs of safe steps that change on a second pass: " + str(broken) + " of " + str(seen) + ".") ^0
("Pairs where the second pass changes exactly when the first step has work left: " + str(agree) + " of " + str(seen) + ", on these samples.") ^0
"A pair stays safe while its second step never hands back text its first step would still change." ^0
if len(undone) > 0:
clean.names()[undone[0] - 1] => first
clean.names()[undone[1] - 1] => second
("The converse does not hold: with " + step_list([undone[0], undone[1]]) + ", a second pass changes nothing, yet " + first + " would still change " + q(undone[2]) + " - " + second + " takes the change back every time.") ^0
"== Input sanitizer ==" ^0
"Cleanup steps for typed text: which are safe to repeat, and which pipelines of safe steps are not." ^0
True => running
while running:
"" ^0
"1) steps 2) samples 3) run a pipeline 4) every pair 5) quit" ^0
plain(input("choice> ")) => choice
if choice == "1":
show_steps()
elif choice == "2":
show_samples()
elif choice == "3":
run_pipeline()
elif choice == "4":
pairs()
elif choice == "5":
False => running
else:
"Pick a number from 1 to 5." ^0
"Bye." ^0
Python projection (main.py)
import clean
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 q(s):
return "'" + s + "'"
def of_samples(n):
return str(n) + " of " + str(len(clean.samples()))
def show_steps():
print("The steps, each run twice on every sample:")
k = 1
for name in clean.names():
print(" " + str(k) + " " + pad(name, 17) + clean.what()[k - 1])
bad = 0
witness = ""
for x in clean.samples():
if not clean.settles([k], x):
bad = bad + 1
if witness == "":
once = clean.step(k, x)
witness = q(x) + " -> " + q(once) + " -> " + q(clean.step(k, once))
if bad == 0:
print(" safe to repeat on all " + str(len(clean.samples())) + " samples")
else:
print(" changes again on " + of_samples(bad) + " samples, as " + witness)
k = k + 1
def show_samples():
print("The samples:")
k = 1
for x in clean.samples():
print(" " + str(k) + " " + q(x))
k = k + 1
def step_list(steps):
out = ""
i = 0
for k in steps:
if i == 0:
out = clean.names()[k - 1]
else:
out = out + ", then " + clean.names()[k - 1]
i = i + 1
return out
def read_steps():
t = plain(input("steps, in order (digits 1 to 6, e.g. 13)> "))
if t == "":
return []
if len(t) > 6:
return [0]
steps = []
for c in t:
if c < "1" or c > "6":
return [0]
steps = steps + [int(c)]
return steps
def run_pipeline():
steps = read_steps()
if len(steps) == 0:
print("Cancelled.")
return
if steps[0] == 0:
print("Type 1 to 6 steps, each a digit from 1 to 6.")
return
t = input("text (a sample 1 to 9, or your own)> ")
if t == "":
print("Cancelled.")
return
if len(t) == 1 and t >= "1" and t <= "9":
x = clean.samples()[int(t) - 1]
elif len(t) > 40:
print("Type a sample number or a text of up to 40 characters.")
return
else:
x = t
once = clean.run(steps, x)
twice = clean.run(steps, once)
thrice = clean.run(steps, twice)
print("Pipeline: " + step_list(steps) + ".")
print(" text: " + q(x))
print(" one pass: " + q(once))
print(" two passes: " + q(twice))
print(" three passes: " + q(thrice))
if twice == once:
print("A second pass changes nothing: replayed, this pipeline gives the same text.")
w = clean.work_left(steps, once)
if w >= 0:
print("Yet " + clean.names()[steps[w] - 1] + " would still change " + q(once) + ": a step after it takes the change back every time.")
else:
print("The second pass changed it: replayed, this pipeline does not give the same text.")
w = clean.work_left(steps, once)
k = steps[w]
if k == 5:
print("Escape every & changes the text again on every pass: it is not safe to repeat even alone.")
else:
print("After one pass, " + clean.names()[k - 1] + " would still change the text: a step after it handed back work it had already done.")
def pairs():
safe = [1, 2, 3, 4, 6]
print("Every ordered pair of the safe steps, run twice on every sample:")
print(" " + pad("pair", 34) + pad("second pass", 20) + "first step has work left")
broken = 0
agree = 0
seen = 0
undone = []
for p in safe:
for r in safe:
if p != r:
bad = 0
left = 0
for x in clean.samples():
if not clean.settles([p, r], x):
bad = bad + 1
h = clean.run([p, r], x)
if clean.step(p, h) != h:
left = left + 1
if clean.settles([p, r], x) and len(undone) == 0:
undone = [p, r, h]
if bad == 0:
verdict = "same"
else:
verdict = "changes on " + of_samples(bad)
broken = broken + 1
if left == 0:
lw = "none"
else:
lw = "on " + of_samples(left)
print(" " + pad(step_list([p, r]), 34) + pad(verdict, 20) + lw)
if (bad == 0) == (left == 0):
agree = agree + 1
seen = seen + 1
print("Pairs of safe steps that change on a second pass: " + str(broken) + " of " + str(seen) + ".")
print("Pairs where the second pass changes exactly when the first step has work left: " + str(agree) + " of " + str(seen) + ", on these samples.")
print("A pair stays safe while its second step never hands back text its first step would still change.")
if len(undone) > 0:
first = clean.names()[undone[0] - 1]
second = clean.names()[undone[1] - 1]
print("The converse does not hold: with " + step_list([undone[0], undone[1]]) + ", a second pass changes nothing, yet " + first + " would still change " + q(undone[2]) + " - " + second + " takes the change back every time.")
print("== Input sanitizer ==")
print("Cleanup steps for typed text: which are safe to repeat, and which pipelines of safe steps are not.")
running = True
while running:
print("")
print("1) steps 2) samples 3) run a pipeline 4) every pair 5) quit")
choice = plain(input("choice> "))
if choice == "1":
show_steps()
elif choice == "2":
show_samples()
elif choice == "3":
run_pipeline()
elif choice == "4":
pairs()
elif choice == "5":
running = False
else:
print("Pick a number from 1 to 5.")
print("Bye.")
clean.eml
eml# P142 input sanitizer - cleanup steps for text people type, and the
# pipelines built from them. As in the corpus case idempotence-witness, a
# step is safe to retry when applying it again changes nothing: escaping
# every "&" is not, escaping only a bare "&" is. As in the corpus case
# idempotence-does-not-compose, a pipeline of steps that are each safe can
# still change on the second pass - a queue that redelivers replays the
# whole pipeline - and it does when a later step hands back text an earlier
# step still has work on.
def names():
return ["trim", "squeeze", "unbracket", "cut", "escape every &", "escape a bare &"]
def what():
return ["take the spaces off both ends", "make each run of spaces one space", "delete every < and >", "keep the first 16 characters", "write each & as &", "write a & as & unless it already begins &"]
def samples():
return [" Hello world ", "Tom & Jerry", "a < b > c", "x <", "<b>Bold</b> move", "Salt & pepper, please", "AT&T", "one two three four", "Fresh breads & jam"]
def trim(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 squeeze(s):
"" => out
for c in s:
if c == " " and len(out) > 0 and out[len(out) - 1] == " ":
pass
else:
out + c => out
return out
def unbracket(s):
"" => out
for c in s:
if c != "<" and c != ">":
out + c => out
return out
def cut(s):
return s[0:16]
def escape_every(s):
"" => out
for c in s:
if c == "&":
out + "&" => out
else:
out + c => out
return out
def escape_bare(s):
"" => out
0 => i
while i < len(s):
if s[i] == "&" and s[i:i + 5] == "&":
out + "&" => out
i + 5 => i
elif s[i] == "&":
out + "&" => out
i + 1 => i
else:
out + s[i] => out
i + 1 => i
return out
def step(k, s):
if k == 1:
return trim(s)
if k == 2:
return squeeze(s)
if k == 3:
return unbracket(s)
if k == 4:
return cut(s)
if k == 5:
return escape_every(s)
return escape_bare(s)
def run(steps, s):
s => out
for k in steps:
step(k, out) => out
return out
def settles(steps, s):
# the pipeline is idempotent on s: a second pass changes nothing
run(steps, s) => once
return run(steps, once) == once
def work_left(steps, h):
# the position of the first step that would still change h, or -1
0 => i
for k in steps:
if step(k, h) != h:
return i
i + 1 => i
return 0 - 1
Python projection (clean.py)
def names():
return ["trim", "squeeze", "unbracket", "cut", "escape every &", "escape a bare &"]
def what():
return ["take the spaces off both ends", "make each run of spaces one space", "delete every < and >", "keep the first 16 characters", "write each & as &", "write a & as & unless it already begins &"]
def samples():
return [" Hello world ", "Tom & Jerry", "a < b > c", "x <", "<b>Bold</b> move", "Salt & pepper, please", "AT&T", "one two three four", "Fresh breads & jam"]
def trim(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 squeeze(s):
out = ""
for c in s:
if c == " " and len(out) > 0 and out[len(out) - 1] == " ":
pass
else:
out = out + c
return out
def unbracket(s):
out = ""
for c in s:
if c != "<" and c != ">":
out = out + c
return out
def cut(s):
return s[0:16]
def escape_every(s):
out = ""
for c in s:
if c == "&":
out = out + "&"
else:
out = out + c
return out
def escape_bare(s):
out = ""
i = 0
while i < len(s):
if s[i] == "&" and s[i:i + 5] == "&":
out = out + "&"
i = i + 5
elif s[i] == "&":
out = out + "&"
i = i + 1
else:
out = out + s[i]
i = i + 1
return out
def step(k, s):
if k == 1:
return trim(s)
if k == 2:
return squeeze(s)
if k == 3:
return unbracket(s)
if k == 4:
return cut(s)
if k == 5:
return escape_every(s)
return escape_bare(s)
def run(steps, s):
out = s
for k in steps:
out = step(k, out)
return out
def settles(steps, s):
once = run(steps, s)
return run(steps, once) == once
def work_left(steps, h):
i = 0
for k in steps:
if step(k, h) != h:
return i
i = i + 1
return 0 - 1