Project P142

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.

2 modules · 2 recorded sessionstext-menu UI in the terminalupdated 2026-10-18

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 steps
  • clean.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 & Jerry becomes Tom &amp; Jerry and then Tom &amp;amp; Jerry. Escaping only an & that does not already begin &amp; 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 turns x < into x , 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 change Fresh 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&amp;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 &amp; B'
  two passes:    'A &amp;amp; B'
  three passes:  'A &amp;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 &amp;
      changes again on 4 of 9 samples, as 'Tom & Jerry' -> 'Tom &amp; Jerry' -> 'Tom &amp;amp; Jerry'
  6 escape a bare &  write a & as &amp; unless it already begins &amp;
      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&amp;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 &amp; Jerry'
  two passes:    'Tom &amp;amp; Jerry'
  three passes:  'Tom &amp;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&amp;T'
  one pass:      'AT&amp;T'
  two passes:    'AT&amp;T'
  three passes:  'AT&amp;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 &amp;", "write a & as &amp; unless it already begins &amp;"]

def samples():
    return ["  Hello   world  ", "Tom & Jerry", "a < b > c", "x <", "<b>Bold</b>  move", "Salt & pepper, please", "AT&amp;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 + "&amp;" => 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] == "&amp;":
            out + "&amp;" => out
            i + 5 => i
        elif s[i] == "&":
            out + "&amp;" => 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 &amp;", "write a & as &amp; unless it already begins &amp;"]

def samples():
    return ["  Hello   world  ", "Tom & Jerry", "a < b > c", "x <", "<b>Bold</b>  move", "Salt & pepper, please", "AT&amp;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 + "&amp;"
        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] == "&amp;":
            out = out + "&amp;"
            i = i + 5
        elif s[i] == "&":
            out = out + "&amp;"
            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

Built on these corpus cases

ΣEML 2026

Efficient New Language — a high-density semantic overlay for humans and AI agents.

This site’s demos run on the real EML toolchain, in your browser.

© 2026 EveMissLab (一言諾科技有限公司) / Neo.K. All rights reserved.