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.
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 itemsoutline.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