Project P126

Outline writer

An outline written as an indented triple-quoted string, read into a tree and written out as bullets, numbers and wrapped lines with a hanging indent; cut to a depth, a with block keeps the indentation where keeping it by hand drifts.

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

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

An outline written in the program as an indented triple-quoted string, read into a tree, and written out as bullets, as numbered items, and wrapped to a width with each line after the first lined up under its text.

  • main.eml - the menu, the source string, the three ways of writing the outline, cutting it to a depth, and adding items
  • outline.eml - the source, splitting and dedenting its lines, reading them into a tree, the writer and its with block, and the renderings

How each part works:

  • As in the corpus case multiline-help-text, a triple-quoted string keeps what the source layout put in it: here an empty line after the opening quotes, an empty one before the closing quotes, and 4 spaces shared by every line. Reading it drops the blank lines at the ends and takes the shared spaces off; then each level is 2 more spaces than the one above. An odd indentation, or one more than a level deeper, is refused with its line number.
  • Wrapping puts as many words on a line as fit beside the item's number; a word longer than the room left goes on a line of its own and runs past the width, and the program counts those lines.
  • As in the corpus case indented-report-writer, the writer keeps its depth with a with block, which steps back on every way out. Cutting the outline to a depth returns early under each item whose children are cut. Kept by hand instead, each early return skips the step back and leaves every later line one more step too far in.

What is checked: menu choices 1 to 7; a width from 20 to 80 and a depth from 0 to 3; an item's number such as 1.2, or 0 for the top, and a text of up to 120 characters. An empty answer cancels.

Sessions: sessions/basic.in gives: - the source string, the bullets and the numbered items; - wrapped at 40, and at 20 (two words longer than the room); - cut to depth 1, where the depth by hand drifts, and to depth 3, where nothing is cut and the two agree; - Tell Splice added under 1.3 and Rest at the top, and the numbers again.

sessions/bad-input.in gives: - menu choices 0 and x; - widths: a cancel, 19, 81 and x; depths: a cancel, 4 and -1; - items: a cancel, 3, 1.9, 1..2, a and 1.1.1.1, then under 1 a cancel and 121 characters, and Sub-step under 1.1.1, cut to depth 3.

Built on the verified corpus cases indented-report-writer (a with block that keeps the shape of the output) and multiline-help-text (what a triple-quoted string captures from the source layout).

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
== Outline writer ==
An outline written as an indented string, read into a tree, and written out as bullets, as numbers, and wrapped to a width.

1) source  2) bullets  3) numbered  4) wrapped  5) to a depth  6) add an item  7) quit
choice> 0
Pick a number from 1 to 7.

1) source  2) bullets  3) numbered  4) wrapped  5) to a depth  6) add an item  7) quit
choice> x
Pick a number from 1 to 7.

1) source  2) bullets  3) numbered  4) wrapped  5) to a depth  6) add an item  7) quit
choice> 4
width (20 to 80)> 
Cancelled.

1) source  2) bullets  3) numbered  4) wrapped  5) to a depth  6) add an item  7) quit
choice> 4
width (20 to 80)> 19
Type a whole number from 20 to 80.

1) source  2) bullets  3) numbered  4) wrapped  5) to a depth  6) add an item  7) quit
choice> 4
width (20 to 80)> 81
Type a whole number from 20 to 80.

1) source  2) bullets  3) numbered  4) wrapped  5) to a depth  6) add an item  7) quit
choice> 4
width (20 to 80)> x
Type a whole number from 20 to 80.

1) source  2) bullets  3) numbered  4) wrapped  5) to a depth  6) add an item  7) quit
choice> 5
depth (0 to 3)> 
Cancelled.

1) source  2) bullets  3) numbered  4) wrapped  5) to a depth  6) add an item  7) quit
choice> 5
depth (0 to 3)> 4
Type a whole number from 0 to 3.

1) source  2) bullets  3) numbered  4) wrapped  5) to a depth  6) add an item  7) quit
choice> 5
depth (0 to 3)> -1
Type a whole number from 0 to 3.

1) source  2) bullets  3) numbered  4) wrapped  5) to a depth  6) add an item  7) quit
choice> 6
under (an item's number such as 1.2, or 0 for the top)> 
Cancelled.

1) source  2) bullets  3) numbered  4) wrapped  5) to a depth  6) add an item  7) quit
choice> 6
under (an item's number such as 1.2, or 0 for the top)> 3
Type the number of an item, such as 1 or 1.2, or 0.

1) source  2) bullets  3) numbered  4) wrapped  5) to a depth  6) add an item  7) quit
choice> 6
under (an item's number such as 1.2, or 0 for the top)> 1.9
Type the number of an item, such as 1 or 1.2, or 0.

1) source  2) bullets  3) numbered  4) wrapped  5) to a depth  6) add an item  7) quit
choice> 6
under (an item's number such as 1.2, or 0 for the top)> 1..2
Type the number of an item, such as 1 or 1.2, or 0.

1) source  2) bullets  3) numbered  4) wrapped  5) to a depth  6) add an item  7) quit
choice> 6
under (an item's number such as 1.2, or 0 for the top)> a
Type the number of an item, such as 1 or 1.2, or 0.

1) source  2) bullets  3) numbered  4) wrapped  5) to a depth  6) add an item  7) quit
choice> 6
under (an item's number such as 1.2, or 0 for the top)> 1.1.1.1
Type the number of an item, such as 1 or 1.2, or 0.

1) source  2) bullets  3) numbered  4) wrapped  5) to a depth  6) add an item  7) quit
choice> 6
under (an item's number such as 1.2, or 0 for the top)> 1
text> 
Cancelled.

1) source  2) bullets  3) numbered  4) wrapped  5) to a depth  6) add an item  7) quit
choice> 6
under (an item's number such as 1.2, or 0 for the top)> 1
text> xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Up to 120 characters.

1) source  2) bullets  3) numbered  4) wrapped  5) to a depth  6) add an item  7) quit
choice> 6
under (an item's number such as 1.2, or 0 for the top)> 1.1.1
text> Sub-step
Added under 1.1.1: Sub-step

1) source  2) bullets  3) numbered  4) wrapped  5) to a depth  6) add an item  7) quit
choice> 5
depth (0 to 3)> 3
With a with block:
Release plan
  Build
    Compile the interpreter and the transpiler for every platform we ship
      Sub-step
    Run the whole test suite
  Check
    Read every golden line by line
    Cross-check each claim a second way, written separately
  Publish
    Build the site
    Deploy
Afterwards
  Write the journal
Keeping the depth by hand, an early return skipping the step back:
Release plan
  Build
    Compile the interpreter and the transpiler for every platform we ship
      Sub-step
    Run the whole test suite
  Check
    Read every golden line by line
    Cross-check each claim a second way, written separately
  Publish
    Build the site
    Deploy
Afterwards
  Write the journal
The two agree: nothing was cut, so no early return happened.

1) source  2) bullets  3) numbered  4) wrapped  5) to a depth  6) add an item  7) quit
choice> 7
Bye.
What was typed (40 lines)
0
x
4

4
19
4
81
4
x
5

5
4
5
-1
6

6
3
6
1.9
6
1..2
6
a
6
1.1.1.1
6
1

6
1
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
6
1.1.1
Sub-step
5
3
7

basic

interpreter: byte-equal
== Outline writer ==
An outline written as an indented string, read into a tree, and written out as bullets, as numbers, and wrapped to a width.

1) source  2) bullets  3) numbered  4) wrapped  5) to a depth  6) add an item  7) quit
choice> 1
The source string has 347 characters in 14 lines (| marks where each line starts):
   1 |
   2 |    Release plan
   3 |      Build
   4 |        Compile the interpreter and the transpiler for every platform we ship
   5 |        Run the whole test suite
   6 |      Check
   7 |        Read every golden line by line
   8 |        Cross-check each claim a second way, written separately
   9 |      Publish
  10 |        Build the site
  11 |        Deploy
  12 |    Afterwards
  13 |      Write the journal
  14 |
Reading it: 2 blank lines at the ends dropped (the empty line right after the opening quotes, and the empty one before the closing quotes), and the 4 spaces every line shares taken off.

1) source  2) bullets  3) numbered  4) wrapped  5) to a depth  6) add an item  7) quit
choice> 2
- Release plan
  * Build
    + Compile the interpreter and the transpiler for every platform we ship
    + Run the whole test suite
  * Check
    + Read every golden line by line
    + Cross-check each claim a second way, written separately
  * Publish
    + Build the site
    + Deploy
- Afterwards
  * Write the journal

1) source  2) bullets  3) numbered  4) wrapped  5) to a depth  6) add an item  7) quit
choice> 3
1 Release plan
  1.1 Build
    1.1.1 Compile the interpreter and the transpiler for every platform we ship
    1.1.2 Run the whole test suite
  1.2 Check
    1.2.1 Read every golden line by line
    1.2.2 Cross-check each claim a second way, written separately
  1.3 Publish
    1.3.1 Build the site
    1.3.2 Deploy
2 Afterwards
  2.1 Write the journal

1) source  2) bullets  3) numbered  4) wrapped  5) to a depth  6) add an item  7) quit
choice> 4
width (20 to 80)> 40
Wrapped at 40, each line after the first lined up under its text:
+----------------------------------------+
1 Release plan
  1.1 Build
    1.1.1 Compile the interpreter and
          the transpiler for every
          platform we ship
    1.1.2 Run the whole test suite
  1.2 Check
    1.2.1 Read every golden line by line
    1.2.2 Cross-check each claim a
          second way, written separately
  1.3 Publish
    1.3.1 Build the site
    1.3.2 Deploy
2 Afterwards
  2.1 Write the journal
+----------------------------------------+

1) source  2) bullets  3) numbered  4) wrapped  5) to a depth  6) add an item  7) quit
choice> 4
width (20 to 80)> 20
Wrapped at 20, each line after the first lined up under its text:
+--------------------+
1 Release plan
  1.1 Build
    1.1.1 Compile
          the
          interpreter   <- past the width
          and the
          transpiler
          for every
          platform
          we ship
    1.1.2 Run the
          whole test
          suite
  1.2 Check
    1.2.1 Read every
          golden
          line by
          line
    1.2.2 Cross-check   <- past the width
          each claim
          a second
          way,
          written
          separately
  1.3 Publish
    1.3.1 Build the
          site
    1.3.2 Deploy
2 Afterwards
  2.1 Write the
      journal
+--------------------+
2 lines run past the width: words longer than the room left beside their numbers.

1) source  2) bullets  3) numbered  4) wrapped  5) to a depth  6) add an item  7) quit
choice> 5
depth (0 to 3)> 1
With a with block:
Release plan
  Build
    (2 more)
  Check
    (2 more)
  Publish
    (2 more)
Afterwards
  Write the journal
Keeping the depth by hand, an early return skipping the step back:
Release plan
  Build
    (2 more)
    Check
      (2 more)
      Publish
        (2 more)
      Afterwards
        Write the journal
6 of the 9 lines differ: each early return leaves every later line by hand one more step too far in.

1) source  2) bullets  3) numbered  4) wrapped  5) to a depth  6) add an item  7) quit
choice> 5
depth (0 to 3)> 3
With a with block:
Release plan
  Build
    Compile the interpreter and the transpiler for every platform we ship
    Run the whole test suite
  Check
    Read every golden line by line
    Cross-check each claim a second way, written separately
  Publish
    Build the site
    Deploy
Afterwards
  Write the journal
Keeping the depth by hand, an early return skipping the step back:
Release plan
  Build
    Compile the interpreter and the transpiler for every platform we ship
    Run the whole test suite
  Check
    Read every golden line by line
    Cross-check each claim a second way, written separately
  Publish
    Build the site
    Deploy
Afterwards
  Write the journal
The two agree: nothing was cut, so no early return happened.

1) source  2) bullets  3) numbered  4) wrapped  5) to a depth  6) add an item  7) quit
choice> 6
under (an item's number such as 1.2, or 0 for the top)> 1.3
text> Tell Splice
Added under 1.3: Tell Splice

1) source  2) bullets  3) numbered  4) wrapped  5) to a depth  6) add an item  7) quit
choice> 6
under (an item's number such as 1.2, or 0 for the top)> 0
text> Rest
Added at the top: Rest

1) source  2) bullets  3) numbered  4) wrapped  5) to a depth  6) add an item  7) quit
choice> 3
1 Release plan
  1.1 Build
    1.1.1 Compile the interpreter and the transpiler for every platform we ship
    1.1.2 Run the whole test suite
  1.2 Check
    1.2.1 Read every golden line by line
    1.2.2 Cross-check each claim a second way, written separately
  1.3 Publish
    1.3.1 Build the site
    1.3.2 Deploy
    1.3.3 Tell Splice
2 Afterwards
  2.1 Write the journal
3 Rest

1) source  2) bullets  3) numbered  4) wrapped  5) to a depth  6) add an item  7) quit
choice> 7
Bye.
What was typed (19 lines)
1
2
3
4
40
4
20
5
1
5
3
6
1.3
Tell Splice
6
0
Rest
3
7

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
# P126 outline writer: the outline's source string, bullets, numbers,
# wrapping to a width, cutting to a depth with and without a with block,
# and adding items.
import outline

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 lpad(s, n):
    s => out
    while len(out) < n:
        " " + out => out
    return out

def say(lines):
    for s in lines:
        s ^0

def show_source():
    outline.SOURCE => s
    outline.split_lines(s) => lines
    ("The source string has " + str(len(s)) + " characters in " + str(len(lines)) + " lines (| marks where each line starts):") ^0
    for k in [0:len(lines) - 1]:
        ("  " + lpad(str(k + 1), 2) + " |" + lines[k]) ^0
    outline.dedent(lines) => d
    ("Reading it: " + str(d[1]) + " blank lines at the ends dropped (the empty line right after the opening quotes, and the empty one before the closing quotes), and the " + str(d[2]) + " spaces every line shares taken off.") ^0

def tree_of():
    outline.dedent(outline.split_lines(outline.SOURCE))[0] => lines
    return outline.parse(lines)[0]

def ask_int(prompt, low, high):
    plain(input(prompt)) => t
    if t == "":
        "Cancelled." ^0
        return 0 - 1
    if len(t) > 2:
        ("Type a whole number from " + str(low) + " to " + str(high) + ".") ^0
        return 0 - 1
    for c in t:
        if c < "0" or c > "9":
            ("Type a whole number from " + str(low) + " to " + str(high) + ".") ^0
            return 0 - 1
    if int(t) < low or int(t) > high:
        ("Type a whole number from " + str(low) + " to " + str(high) + ".") ^0
        return 0 - 1
    return int(t)

def wrapped(tree):
    ask_int("width (20 to 80)> ", 20, 80) => w
    if w < 0:
        return
    outline.numbered(tree, w) => lines
    ("Wrapped at " + str(w) + ", each line after the first lined up under its text:") ^0
    "+" + "-" * w + "+" => ruler
    ruler ^0
    0 => over
    for s in lines:
        if len(s) > w:
            over + 1 => over
            (s + "   <- past the width") ^0
        else:
            s ^0
    ruler ^0
    if over == 1:
        "1 line runs past the width: a word longer than the room left beside its number." ^0
    elif over > 1:
        (str(over) + " lines run past the width: words longer than the room left beside their numbers.") ^0

def cut(tree):
    ask_int("depth (0 to 3)> ", 0, 3) => d
    if d < 0:
        return
    outline.to_depth(tree, d, True) => a
    outline.to_depth(tree, d, False) => b
    "With a with block:" ^0
    say(a)
    "Keeping the depth by hand, an early return skipping the step back:" ^0
    say(b)
    0 => differ
    for k in [0:len(a) - 1]:
        if a[k] != b[k]:
            differ + 1 => differ
    if differ == 0:
        "The two agree: nothing was cut, so no early return happened." ^0
    else:
        (str(differ) + " of the " + str(len(a)) + " lines differ: each early return leaves every later line by hand one more step too far in.") ^0

def find_path(tree, t):
    # the list of children under a path such as 1.2, or [] with ok False
    if t == "0":
        return [True, []]
    [] => nums
    "" => w
    for c in t + ".":
        if c == ".":
            if w == "" or len(w) > 2:
                return [False, []]
            nums + [int(w)] => nums
            "" => w
        elif c >= "0" and c <= "9":
            w + c => w
        else:
            return [False, []]
    tree => level
    for n in nums:
        if n < 1 or n > len(level):
            return [False, []]
        level[n - 1][1] => level
    return [True, nums]

def add_item(tree):
    plain(input("under (an item's number such as 1.2, or 0 for the top)> ")) => t
    if t == "":
        "Cancelled." ^0
        return tree
    find_path(tree, t) => p
    if not p[0]:
        "Type the number of an item, such as 1 or 1.2, or 0." ^0
        return tree
    plain(input("text> ")) => text
    if text == "":
        "Cancelled." ^0
        return tree
    if len(text) > 120:
        "Up to 120 characters." ^0
        return tree
    add_at(tree, p[1], text) => out
    if len(p[1]) == 0:
        ("Added at the top: " + text) ^0
    else:
        ("Added under " + t + ": " + text) ^0
    return out

def add_at(level, nums, text):
    if len(nums) == 0:
        return level + [[text, []]]
    [] => out
    for i in [0:len(level) - 1]:
        if i == nums[0] - 1:
            out + [[level[i][0], add_at(level[i][1], nums[1:len(nums)], text)]] => out
        else:
            out + [level[i]] => out
    return out

tree_of() => tree

"== Outline writer ==" ^0
"An outline written as an indented string, read into a tree, and written out as bullets, as numbers, and wrapped to a width." ^0
True => running
while running:
    "" ^0
    "1) source  2) bullets  3) numbered  4) wrapped  5) to a depth  6) add an item  7) quit" ^0
    plain(input("choice> ")) => choice
    if choice == "1":
        show_source()
    elif choice == "2":
        say(outline.bullets(tree))
    elif choice == "3":
        say(outline.numbered(tree, 0))
    elif choice == "4":
        wrapped(tree)
    elif choice == "5":
        cut(tree)
    elif choice == "6":
        add_item(tree) => tree
    elif choice == "7":
        False => running
    else:
        "Pick a number from 1 to 7." ^0
"Bye." ^0
Python projection (main.py)
import outline

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 lpad(s, n):
    out = s
    while len(out) < n:
        out = " " + out
    return out

def say(lines):
    for s in lines:
        print(s)

def show_source():
    s = outline.SOURCE
    lines = outline.split_lines(s)
    print("The source string has " + str(len(s)) + " characters in " + str(len(lines)) + " lines (| marks where each line starts):")
    for k in range(0, len(lines)):
        print("  " + lpad(str(k + 1), 2) + " |" + lines[k])
    d = outline.dedent(lines)
    print("Reading it: " + str(d[1]) + " blank lines at the ends dropped (the empty line right after the opening quotes, and the empty one before the closing quotes), and the " + str(d[2]) + " spaces every line shares taken off.")

def tree_of():
    lines = outline.dedent(outline.split_lines(outline.SOURCE))[0]
    return outline.parse(lines)[0]

def ask_int(prompt, low, high):
    t = plain(input(prompt))
    if t == "":
        print("Cancelled.")
        return 0 - 1
    if len(t) > 2:
        print("Type a whole number from " + str(low) + " to " + str(high) + ".")
        return 0 - 1
    for c in t:
        if c < "0" or c > "9":
            print("Type a whole number from " + str(low) + " to " + str(high) + ".")
            return 0 - 1
    if int(t) < low or int(t) > high:
        print("Type a whole number from " + str(low) + " to " + str(high) + ".")
        return 0 - 1
    return int(t)

def wrapped(tree):
    w = ask_int("width (20 to 80)> ", 20, 80)
    if w < 0:
        return
    lines = outline.numbered(tree, w)
    print("Wrapped at " + str(w) + ", each line after the first lined up under its text:")
    ruler = "+" + "-" * w + "+"
    print(ruler)
    over = 0
    for s in lines:
        if len(s) > w:
            over = over + 1
            print(s + "   <- past the width")
        else:
            print(s)
    print(ruler)
    if over == 1:
        print("1 line runs past the width: a word longer than the room left beside its number.")
    elif over > 1:
        print(str(over) + " lines run past the width: words longer than the room left beside their numbers.")

def cut(tree):
    d = ask_int("depth (0 to 3)> ", 0, 3)
    if d < 0:
        return
    a = outline.to_depth(tree, d, True)
    b = outline.to_depth(tree, d, False)
    print("With a with block:")
    say(a)
    print("Keeping the depth by hand, an early return skipping the step back:")
    say(b)
    differ = 0
    for k in range(0, len(a)):
        if a[k] != b[k]:
            differ = differ + 1
    if differ == 0:
        print("The two agree: nothing was cut, so no early return happened.")
    else:
        print(str(differ) + " of the " + str(len(a)) + " lines differ: each early return leaves every later line by hand one more step too far in.")

def find_path(tree, t):
    if t == "0":
        return [True, []]
    nums = []
    w = ""
    for c in t + ".":
        if c == ".":
            if w == "" or len(w) > 2:
                return [False, []]
            nums = nums + [int(w)]
            w = ""
        elif c >= "0" and c <= "9":
            w = w + c
        else:
            return [False, []]
    level = tree
    for n in nums:
        if n < 1 or n > len(level):
            return [False, []]
        level = level[n - 1][1]
    return [True, nums]

def add_item(tree):
    t = plain(input("under (an item's number such as 1.2, or 0 for the top)> "))
    if t == "":
        print("Cancelled.")
        return tree
    p = find_path(tree, t)
    if not p[0]:
        print("Type the number of an item, such as 1 or 1.2, or 0.")
        return tree
    text = plain(input("text> "))
    if text == "":
        print("Cancelled.")
        return tree
    if len(text) > 120:
        print("Up to 120 characters.")
        return tree
    out = add_at(tree, p[1], text)
    if len(p[1]) == 0:
        print("Added at the top: " + text)
    else:
        print("Added under " + t + ": " + text)
    return out

def add_at(level, nums, text):
    if len(nums) == 0:
        return level + [[text, []]]
    out = []
    for i in range(0, len(level)):
        if i == nums[0] - 1:
            out = out + [[level[i][0], add_at(level[i][1], nums[1:len(nums)], text)]]
        else:
            out = out + [level[i]]
    return out

tree = tree_of()
print("== Outline writer ==")
print("An outline written as an indented string, read into a tree, and written out as bullets, as numbers, and wrapped to a width.")
running = True
while running:
    print("")
    print("1) source  2) bullets  3) numbered  4) wrapped  5) to a depth  6) add an item  7) quit")
    choice = plain(input("choice> "))
    if choice == "1":
        show_source()
    elif choice == "2":
        say(outline.bullets(tree))
    elif choice == "3":
        say(outline.numbered(tree, 0))
    elif choice == "4":
        wrapped(tree)
    elif choice == "5":
        cut(tree)
    elif choice == "6":
        tree = add_item(tree)
    elif choice == "7":
        running = False
    else:
        print("Pick a number from 1 to 7.")
print("Bye.")

outline.eml

eml
# P126 outline writer - an outline written as an indented triple-quoted
# string, read into a tree, and written out as bullets, as numbered items,
# and wrapped to a width with a hanging indent. As in the corpus case
# multiline-help-text, the string keeps the newline after its opening
# quotes and the indentation of its lines, and both have to be taken off.
# As in indented-report-writer, the depth of the output is kept by a with
# block, so it comes back down on every way out, early returns included.
#
# A node is [text, children].

"""
    Release plan
      Build
        Compile the interpreter and the transpiler for every platform we ship
        Run the whole test suite
      Check
        Read every golden line by line
        Cross-check each claim a second way, written separately
      Publish
        Build the site
        Deploy
    Afterwards
      Write the journal
""" => SOURCE

def split_lines(text):
    [] => out
    "" => cur
    for ch in text:
        if ch == "\n":
            out + [cur] => out
            "" => cur
        else:
            cur + ch => cur
    return out + [cur]

def lead(s):
    0 => n
    while n < len(s) and s[n] == " ":
        n + 1 => n
    return n

def blank(s):
    return lead(s) == len(s)

def dedent(lines):
    # [lines with blank ones at either end dropped, the common indentation
    #  taken off; how many blank lines were dropped; the indentation]
    0 => a
    len(lines) => b
    while a < b and blank(lines[a]):
        a + 1 => a
    while b > a and blank(lines[b - 1]):
        b - 1 => b
    0 - 1 => common
    for k in [a:b - 1]:
        if not blank(lines[k]):
            if common < 0 or lead(lines[k]) < common:
                lead(lines[k]) => common
    [] => out
    for k in [a:b - 1]:
        if blank(lines[k]):
            out + [""] => out
        else:
            out + [lines[k][common:len(lines[k])]] => out
    return [out, a + len(lines) - b, max2(common, 0)]

def max2(x, y):
    if x > y:
        return x
    return y

def parse(lines):
    # [tree, ""] or [[], what is wrong]; each level is 2 more spaces than
    # the one above
    [] => roots
    [] => stack
    for k in [0:len(lines) - 1]:
        lines[k] => s
        if not blank(s):
            lead(s) => n
            if n % 2 == 1:
                return [[], "line " + str(k + 1) + " is indented by an odd number of spaces"]
            n / 2 => d
            int(d) => d
            if d > len(stack):
                return [[], "line " + str(k + 1) + " is indented more than one level deeper than the line above"]
            [s[n:len(s)], []] => node
            stack[0:d] => stack
            if d == 0:
                roots + [node] => roots
            else:
                stack[d - 1][1] + [node] => stack[d - 1][1]
            stack + [node] => stack
    return [roots, ""]

class Indent:
    def __init__(self, writer):
        writer => self.writer
    def __enter__(self):
        self.writer.depth + 1 => self.writer.depth
        return self.writer
    def __exit__(self, exc_type, exc_value, tb):
        self.writer.depth - 1 => self.writer.depth
        return False

class Writer:
    def __init__(self):
        0 => self.depth
        [] => self.lines
    def line(self, text):
        self.lines + ["  " * self.depth + text] => self.lines
    def block(self):
        return Indent(self)

def bullets(tree):
    Writer() => w
    for node in tree:
        bullet(w, node)
    return w.lines

def bullet(w, node):
    ["-", "*", "+"] => marks
    w.line(marks[w.depth % 3] + " " + node[0])
    for child in node[1]:
        with w.block():
            bullet(w, child)

def numbered(tree, width):
    # numbered items, wrapped to width (0 for no wrapping), each line after
    # the first lined up under the text
    [] => out
    for i in [0:len(tree) - 1]:
        number(tree[i], str(i + 1), 0, width) => more
        out + more => out
    return out

def number(node, label, depth, width):
    "  " * depth + label + " " => head
    wrap(node[0], width - len(head)) => parts
    [head + parts[0]] => out
    for k in [1:len(parts) - 1]:
        out + [" " * len(head) + parts[k]] => out
    for i in [0:len(node[1]) - 1]:
        number(node[1][i], label + "." + str(i + 1), depth + 1, width) => more
        out + more => out
    return out

def wrap(text, room):
    # the text in lines of at most room characters, a word longer than that
    # on a line of its own; room 0 or less: one line
    if room <= 0:
        return [text]
    [] => out
    "" => cur
    "" => word
    for ch in text + " ":
        if ch == " ":
            if word != "":
                if cur == "":
                    word => cur
                elif len(cur) + 1 + len(word) <= room:
                    cur + " " + word => cur
                else:
                    out + [cur] => out
                    word => cur
            "" => word
        else:
            word + ch => word
    return out + [cur]

def to_depth(tree, most, with_block):
    # the outline to a depth, the children below it summed up; with_block
    # False keeps the depth by hand, and an early return skips the step back
    Writer() => w
    for node in tree:
        if with_block:
            cut_with(w, node, 0, most)
        else:
            cut_by_hand(w, node, 0, most)
    return w.lines

def cut_with(w, node, depth, most):
    w.line(node[0])
    with w.block():
        for child in node[1]:
            if depth + 1 > most:
                w.line("(" + str(len(node[1])) + " more)")
                return
            cut_with(w, child, depth + 1, most)

def cut_by_hand(w, node, depth, most):
    w.line(node[0])
    w.depth + 1 => w.depth
    for child in node[1]:
        if depth + 1 > most:
            w.line("(" + str(len(node[1])) + " more)")
            return
        cut_by_hand(w, child, depth + 1, most)
    w.depth - 1 => w.depth
Python projection (outline.py)
SOURCE = "\n    Release plan\n      Build\n        Compile the interpreter and the transpiler for every platform we ship\n        Run the whole test suite\n      Check\n        Read every golden line by line\n        Cross-check each claim a second way, written separately\n      Publish\n        Build the site\n        Deploy\n    Afterwards\n      Write the journal\n"

def split_lines(text):
    out = []
    cur = ""
    for ch in text:
        if ch == "\n":
            out = out + [cur]
            cur = ""
        else:
            cur = cur + ch
    return out + [cur]

def lead(s):
    n = 0
    while n < len(s) and s[n] == " ":
        n = n + 1
    return n

def blank(s):
    return lead(s) == len(s)

def dedent(lines):
    a = 0
    b = len(lines)
    while a < b and blank(lines[a]):
        a = a + 1
    while b > a and blank(lines[b - 1]):
        b = b - 1
    common = 0 - 1
    for k in range(a, b):
        if not blank(lines[k]):
            if common < 0 or lead(lines[k]) < common:
                common = lead(lines[k])
    out = []
    for k in range(a, b):
        if blank(lines[k]):
            out = out + [""]
        else:
            out = out + [lines[k][common:len(lines[k])]]
    return [out, a + len(lines) - b, max2(common, 0)]

def max2(x, y):
    if x > y:
        return x
    return y

def parse(lines):
    roots = []
    stack = []
    for k in range(0, len(lines)):
        s = lines[k]
        if not blank(s):
            n = lead(s)
            if n % 2 == 1:
                return [[], "line " + str(k + 1) + " is indented by an odd number of spaces"]
            d = n / 2
            d = int(d)
            if d > len(stack):
                return [[], "line " + str(k + 1) + " is indented more than one level deeper than the line above"]
            node = [s[n:len(s)], []]
            stack = stack[0:d]
            if d == 0:
                roots = roots + [node]
            else:
                stack[d - 1][1] = stack[d - 1][1] + [node]
            stack = stack + [node]
    return [roots, ""]

class Indent:
    def __init__(self, writer):
        self.writer = writer
    def __enter__(self):
        self.writer.depth = self.writer.depth + 1
        return self.writer
    def __exit__(self, exc_type, exc_value, tb):
        self.writer.depth = self.writer.depth - 1
        return False

class Writer:
    def __init__(self):
        self.depth = 0
        self.lines = []
    def line(self, text):
        self.lines = self.lines + ["  " * self.depth + text]
    def block(self):
        return Indent(self)

def bullets(tree):
    w = Writer()
    for node in tree:
        bullet(w, node)
    return w.lines

def bullet(w, node):
    marks = ["-", "*", "+"]
    w.line(marks[w.depth % 3] + " " + node[0])
    for child in node[1]:
        with w.block():
            bullet(w, child)

def numbered(tree, width):
    out = []
    for i in range(0, len(tree)):
        more = number(tree[i], str(i + 1), 0, width)
        out = out + more
    return out

def number(node, label, depth, width):
    head = "  " * depth + label + " "
    parts = wrap(node[0], width - len(head))
    out = [head + parts[0]]
    for k in range(1, len(parts)):
        out = out + [" " * len(head) + parts[k]]
    for i in range(0, len(node[1])):
        more = number(node[1][i], label + "." + str(i + 1), depth + 1, width)
        out = out + more
    return out

def wrap(text, room):
    if room <= 0:
        return [text]
    out = []
    cur = ""
    word = ""
    for ch in text + " ":
        if ch == " ":
            if word != "":
                if cur == "":
                    cur = word
                elif len(cur) + 1 + len(word) <= room:
                    cur = cur + " " + word
                else:
                    out = out + [cur]
                    cur = word
            word = ""
        else:
            word = word + ch
    return out + [cur]

def to_depth(tree, most, with_block):
    w = Writer()
    for node in tree:
        if with_block:
            cut_with(w, node, 0, most)
        else:
            cut_by_hand(w, node, 0, most)
    return w.lines

def cut_with(w, node, depth, most):
    w.line(node[0])
    with w.block():
        for child in node[1]:
            if depth + 1 > most:
                w.line("(" + str(len(node[1])) + " more)")
                return
            cut_with(w, child, depth + 1, most)

def cut_by_hand(w, node, depth, most):
    w.line(node[0])
    w.depth = w.depth + 1
    for child in node[1]:
        if depth + 1 > most:
            w.line("(" + str(len(node[1])) + " more)")
            return
        cut_by_hand(w, child, depth + 1, most)
    w.depth = w.depth - 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.