Project P077

Text banner

Big letters five rows high from a built-in font, wrapped to the screen; ordinary text wrapped greedily to a width and lined up left, centred or right; and a frame whose width comes from the longest line, so its edge closes exactly.

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

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

Big letters five rows high, wrapped to fit the screen. Ordinary text wrapped to a width and lined up left, centred or right. And a frame round it that fits the text exactly.

  • main.eml - the menu, reading text and widths, and printing
  • banner.eml - the 5 x 5 font, big letters, wrapping, lining up and framing

How each part works:

  • Big letters: a font of 41 glyphs (A to Z, 0 to 9, space and ! ? . -), each five rows of five columns, with no two alike. Small letters are drawn as capitals. A banner line is the glyphs side by side with one space between, and no spaces left at the ends of the rows. 13 letters fit in 80 columns, so longer text is wrapped first.
  • Wrapping is greedy: as many words as fit on each line, one space between them. A word longer than the width is cut into pieces that fit.
  • Lining up: right puts the spaces in front; centre splits them, with an odd one going on the right. Nothing is added after the text.
  • The frame, as in the corpus case text-banner-builder, is as wide as the longest line plus the padding on both sides, worked out from the text rather than a fixed number. A wrong count would show as a ragged right edge.

What is checked: menu choices 1 to 4; text of up to 200 characters, and for big letters only the characters the font has; a width of 10 to 80 (10 to 70 inside a frame); padding of 0 to 4; left, centre or right. An empty answer cancels.

Sessions: sessions/basic.in gives: - "Hello EML 2026!" in big letters on two lines, and "eml-p rocks?" in capitals; - one sentence wrapped to 30 on the left, and to 20 centred; - a sentence with "internationalisation," cut to fit 15, on the right; - a sentence in a frame, 24 wide with padding 2, centred.

sessions/bad-input.in gives: - menu choices 0 and x; - "a,b", which the font cannot draw, and cancels; - widths 9, 81 and x, the alignment "middle", padding 5, then "abc" in the smallest frame; - a text over 200 characters.

Built on the verified corpus case text-banner-builder (sequence repetition, and a frame drawn to a width taken from the longest line).

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
== Text banner ==
Big letters, wrapped and lined-up text, and a frame that fits.

1) big letters  2) wrap and line up  3) in a frame  4) quit
choice> 0
Pick a number from 1 to 4.

1) big letters  2) wrap and line up  3) in a frame  4) quit
choice> x
Pick a number from 1 to 4.

1) big letters  2) wrap and line up  3) in a frame  4) quit
choice> 1
text for the banner> a,b
The big letters are A to Z, 0 to 9, space and ! ? . -
text for the banner> 
Cancelled.

1) big letters  2) wrap and line up  3) in a frame  4) quit
choice> 2
text> 
Cancelled.

1) big letters  2) wrap and line up  3) in a frame  4) quit
choice> 2
text> abc
width (10 to 80)> 9
Type a whole number from 10 to 80.
width (10 to 80)> 81
Type a whole number from 10 to 80.
width (10 to 80)> x
Type a whole number from 10 to 80.
width (10 to 80)> 20
line up (left, centre or right)> middle
Type left, centre or right.
line up (left, centre or right)> 
Cancelled.

1) big letters  2) wrap and line up  3) in a frame  4) quit
choice> 3
text> abc
width of the text (10 to 70)> 10
padding (0 to 4)> 5
Type a whole number from 0 to 4.
padding (0 to 4)> 0
line up (left, centre or right)> left
+---+
|abc|
+---+

1) big letters  2) wrap and line up  3) in a frame  4) quit
choice> 2
text> word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word
Up to 200 characters.
text> 
Cancelled.

1) big letters  2) wrap and line up  3) in a frame  4) quit
choice> 4
Bye.
What was typed (25 lines)
0
x
1
a,b

2

2
abc
9
81
x
20
middle

3
abc
10
5
0
left
2
word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word

4

basic

interpreter: byte-equal
== Text banner ==
Big letters, wrapped and lined-up text, and a frame that fits.

1) big letters  2) wrap and line up  3) in a frame  4) quit
choice> 1
text for the banner> Hello EML 2026!
#   # ##### #     #      ###        ##### #   # #
#   # #     #     #     #   #       #     ## ## #
##### ####  #     #     #   #       ####  # # # #
#   # #     #     #     #   #       #     #   # #
#   # ##### ##### #####  ###        ##### #   # #####

 ###   ###   ###   ###    #
#   # #  ## #   # #       #
  ##  # # #   ##  ####    #
 #    ##  #  #    #   #
#####  ###  #####  ###    #

1) big letters  2) wrap and line up  3) in a frame  4) quit
choice> 1
text for the banner> eml-p rocks?
##### #   # #           ####        ####   ###   #### #   #  ####  ###
#     ## ## #           #   #       #   # #   # #     #  #  #     #   #
####  # # # #     ##### ####        ####  #   # #     ###    ###    ##
#     #   # #           #           #  #  #   # #     #  #      #
##### #   # #####       #           #   #  ###   #### #   # ####    #

1) big letters  2) wrap and line up  3) in a frame  4) quit
choice> 2
text> The quick brown fox jumps over the lazy dog, again and again.
width (10 to 80)> 30
line up (left, centre or right)> left
+----------------------------+
The quick brown fox jumps over
the lazy dog, again and again.
+----------------------------+
(the rule marks the width, 30 columns)

1) big letters  2) wrap and line up  3) in a frame  4) quit
choice> 2
text> The quick brown fox jumps over the lazy dog, again and again.
width (10 to 80)> 20
line up (left, centre or right)> centre
+------------------+
The quick brown fox
jumps over the lazy
   dog, again and
       again.
+------------------+
(the rule marks the width, 20 columns)

1) big letters  2) wrap and line up  3) in a frame  4) quit
choice> 2
text> Some words are longer than a line: internationalisation, for one.
width (10 to 80)> 15
line up (left, centre or right)> right
+-------------+
 Some words are
  longer than a
          line:
internationalis
ation, for one.
+-------------+
(the rule marks the width, 15 columns)

1) big letters  2) wrap and line up  3) in a frame  4) quit
choice> 3
text> A frame as wide as its longest line, so the right edge closes.
width of the text (10 to 70)> 24
padding (0 to 4)> 2
line up (left, centre or right)> centre
+--------------------------+
|  A frame as wide as its  |
|   longest line, so the   |
|    right edge closes.    |
+--------------------------+

1) big letters  2) wrap and line up  3) in a frame  4) quit
choice> 4
Bye.
What was typed (22 lines)
1
Hello EML 2026!
1
eml-p rocks?
2
The quick brown fox jumps over the lazy dog, again and again.
30
left
2
The quick brown fox jumps over the lazy dog, again and again.
20
centre
2
Some words are longer than a line: internationalisation, for one.
15
right
3
A frame as wide as its longest line, so the right edge closes.
24
2
centre
4

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
# P077 text banner: big five-row letters, wrapped to the screen; ordinary
# text wrapped to a width and lined up left, centred or right; and a frame
# round it.
import banner

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 number(s):
    if len(s) == 0 or len(s) > 2:
        return -1
    0 => n
    for c in s:
        if not (c >= "0" and c <= "9"):
            return -1
        n * 10 + int(c) => n
    return n

def ask(prompt, low, high):
    while True:
        trim(input(prompt + " (" + str(low) + " to " + str(high) + ")> ")) => a
        if a == "":
            return -1
        number(a) => n
        if n >= low and n <= high:
            return n
        ("Type a whole number from " + str(low) + " to " + str(high) + ".") ^0

def ask_text(prompt):
    while True:
        trim(input(prompt)) => s
        if s == "" or len(s) <= 200:
            return s
        "Up to 200 characters." ^0

def big():
    while True:
        ask_text("text for the banner> ") => s
        if s == "":
            "Cancelled." ^0
            return
        if banner.can_draw(s):
            # 13 letters of 5 columns, a space between them, fit in 80.
            banner.wrap(s, 13) => parts
            for k in [0:len(parts) - 1]:
                if k > 0:
                    "" ^0
                for row in banner.big(parts[k]):
                    row ^0
            return
        "The big letters are A to Z, 0 to 9, space and ! ? . -" ^0

def ask_how():
    while True:
        trim(input("line up (left, centre or right)> ")) => h
        if h == "":
            return ""
        if h == "left" or h == "centre" or h == "right":
            return h
        "Type left, centre or right." ^0

def wrapped():
    ask_text("text> ") => s
    if s == "":
        "Cancelled." ^0
        return
    ask("width", 10, 80) => w
    if w == -1:
        "Cancelled." ^0
        return
    ask_how() => how
    if how == "":
        "Cancelled." ^0
        return
    "+" + "-" * (w - 2) + "+" => ruler
    ruler ^0
    for line in banner.wrap(s, w):
        banner.aligned(line, w, how) ^0
    ruler ^0
    ("(the rule marks the width, " + str(w) + " columns)") ^0

def framed():
    ask_text("text> ") => s
    if s == "":
        "Cancelled." ^0
        return
    ask("width of the text", 10, 70) => w
    if w == -1:
        "Cancelled." ^0
        return
    ask("padding", 0, 4) => pad
    if pad == -1:
        "Cancelled." ^0
        return
    ask_how() => how
    if how == "":
        "Cancelled." ^0
        return
    banner.wrap(s, w) => lines
    0 => widest
    for line in lines:
        if len(line) > widest:
            len(line) => widest
    [] => placed
    for line in lines:
        placed + [banner.aligned(line, widest, how)] => placed
    for row in banner.boxed(placed, pad):
        row ^0

"== Text banner ==" ^0
"Big letters, wrapped and lined-up text, and a frame that fits." ^0
True => running
while running:
    "" ^0
    "1) big letters  2) wrap and line up  3) in a frame  4) quit" ^0
    trim(input("choice> ")) => choice
    if choice == "1":
        big()
    elif choice == "2":
        wrapped()
    elif choice == "3":
        framed()
    elif choice == "4":
        False => running
    else:
        "Pick a number from 1 to 4." ^0
"Bye." ^0
Python projection (main.py)
import banner

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 number(s):
    if len(s) == 0 or len(s) > 2:
        return -1
    n = 0
    for c in s:
        if not (c >= "0" and c <= "9"):
            return -1
        n = n * 10 + int(c)
    return n

def ask(prompt, low, high):
    while True:
        a = trim(input(prompt + " (" + str(low) + " to " + str(high) + ")> "))
        if a == "":
            return -1
        n = number(a)
        if n >= low and n <= high:
            return n
        print("Type a whole number from " + str(low) + " to " + str(high) + ".")

def ask_text(prompt):
    while True:
        s = trim(input(prompt))
        if s == "" or len(s) <= 200:
            return s
        print("Up to 200 characters.")

def big():
    while True:
        s = ask_text("text for the banner> ")
        if s == "":
            print("Cancelled.")
            return
        if banner.can_draw(s):
            parts = banner.wrap(s, 13)
            for k in range(0, len(parts)):
                if k > 0:
                    print("")
                for row in banner.big(parts[k]):
                    print(row)
            return
        print("The big letters are A to Z, 0 to 9, space and ! ? . -")

def ask_how():
    while True:
        h = trim(input("line up (left, centre or right)> "))
        if h == "":
            return ""
        if h == "left" or h == "centre" or h == "right":
            return h
        print("Type left, centre or right.")

def wrapped():
    s = ask_text("text> ")
    if s == "":
        print("Cancelled.")
        return
    w = ask("width", 10, 80)
    if w == -1:
        print("Cancelled.")
        return
    how = ask_how()
    if how == "":
        print("Cancelled.")
        return
    ruler = "+" + "-" * (w - 2) + "+"
    print(ruler)
    for line in banner.wrap(s, w):
        print(banner.aligned(line, w, how))
    print(ruler)
    print("(the rule marks the width, " + str(w) + " columns)")

def framed():
    s = ask_text("text> ")
    if s == "":
        print("Cancelled.")
        return
    w = ask("width of the text", 10, 70)
    if w == -1:
        print("Cancelled.")
        return
    pad = ask("padding", 0, 4)
    if pad == -1:
        print("Cancelled.")
        return
    how = ask_how()
    if how == "":
        print("Cancelled.")
        return
    lines = banner.wrap(s, w)
    widest = 0
    for line in lines:
        if len(line) > widest:
            widest = len(line)
    placed = []
    for line in lines:
        placed = placed + [banner.aligned(line, widest, how)]
    for row in banner.boxed(placed, pad):
        print(row)

print("== Text banner ==")
print("Big letters, wrapped and lined-up text, and a frame that fits.")
running = True
while running:
    print("")
    print("1) big letters  2) wrap and line up  3) in a frame  4) quit")
    choice = trim(input("choice> "))
    if choice == "1":
        big()
    elif choice == "2":
        wrapped()
    elif choice == "3":
        framed()
    elif choice == "4":
        running = False
    else:
        print("Pick a number from 1 to 4.")
print("Bye.")

banner.eml

eml
# P077 text banner - big letters five rows high, text wrapped to a width
# and lined up left, centred or right, and a frame drawn round it whose
# width comes from the longest line, as in the corpus case
# text-banner-builder - so a wrong count shows as a ragged edge.

"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 !?.-" => LETTERS
"abcdefghijklmnopqrstuvwxyz" => SMALL

[
    [" ### ", "#   #", "#####", "#   #", "#   #"],
    ["#### ", "#   #", "#### ", "#   #", "#### "],
    [" ####", "#    ", "#    ", "#    ", " ####"],
    ["#### ", "#   #", "#   #", "#   #", "#### "],
    ["#####", "#    ", "#### ", "#    ", "#####"],
    ["#####", "#    ", "#### ", "#    ", "#    "],
    [" ####", "#    ", "#  ##", "#   #", " ####"],
    ["#   #", "#   #", "#####", "#   #", "#   #"],
    ["#####", "  #  ", "  #  ", "  #  ", "#####"],
    ["#####", "   # ", "   # ", "#  # ", " ##  "],
    ["#   #", "#  # ", "###  ", "#  # ", "#   #"],
    ["#    ", "#    ", "#    ", "#    ", "#####"],
    ["#   #", "## ##", "# # #", "#   #", "#   #"],
    ["#   #", "##  #", "# # #", "#  ##", "#   #"],
    [" ### ", "#   #", "#   #", "#   #", " ### "],
    ["#### ", "#   #", "#### ", "#    ", "#    "],
    [" ### ", "#   #", "# # #", "#  # ", " ## #"],
    ["#### ", "#   #", "#### ", "#  # ", "#   #"],
    [" ####", "#    ", " ### ", "    #", "#### "],
    ["#####", "  #  ", "  #  ", "  #  ", "  #  "],
    ["#   #", "#   #", "#   #", "#   #", " ### "],
    ["#   #", "#   #", "#   #", " # # ", "  #  "],
    ["#   #", "#   #", "# # #", "## ##", "#   #"],
    ["#   #", " # # ", "  #  ", " # # ", "#   #"],
    ["#   #", " # # ", "  #  ", "  #  ", "  #  "],
    ["#####", "   # ", "  #  ", " #   ", "#####"],
    [" ### ", "#  ##", "# # #", "##  #", " ### "],
    ["  #  ", " ##  ", "  #  ", "  #  ", " ### "],
    [" ### ", "#   #", "  ## ", " #   ", "#####"],
    ["#### ", "    #", " ### ", "    #", "#### "],
    ["#   #", "#   #", "#####", "    #", "    #"],
    ["#####", "#    ", "#### ", "    #", "#### "],
    [" ### ", "#    ", "#### ", "#   #", " ### "],
    ["#####", "    #", "   # ", "  #  ", "  #  "],
    [" ### ", "#   #", " ### ", "#   #", " ### "],
    [" ### ", "#   #", " ####", "    #", " ### "],
    ["     ", "     ", "     ", "     ", "     "],
    ["  #  ", "  #  ", "  #  ", "     ", "  #  "],
    [" ### ", "#   #", "  ## ", "     ", "  #  "],
    ["     ", "     ", "     ", "     ", "  #  "],
    ["     ", "     ", "#####", "     ", "     "],
] => GLYPHS

def index_in(s, c):
    for i in [0:len(s) - 1]:
        if s[i] == c:
            return i
    return -1

def upper(c):
    index_in(SMALL, c) => i
    if i >= 0:
        return LETTERS[i]
    return c

def can_draw(text):
    for c in text:
        if index_in(LETTERS, upper(c)) == -1:
            return False
    return True

def trim_right(s):
    len(s) => j
    while j > 0 and s[j - 1] == " ":
        j - 1 => j
    return s[0:j]

def big(text):
    # Five rows: each letter's glyph, one space between letters, and no
    # spaces left at the ends of the rows.
    ["", "", "", "", ""] => rows
    for k in [0:len(text) - 1]:
        GLYPHS[index_in(LETTERS, upper(text[k]))] => g
        for r in [0:4]:
            if k > 0:
                rows[r] + " " => rows[r]
            rows[r] + g[r] => rows[r]
    for r in [0:4]:
        trim_right(rows[r]) => rows[r]
    return rows

def words(s):
    [] => out
    "" => w
    for c in s + " ":
        if c == " ":
            if w != "":
                out + [w] => out
            "" => w
        else:
            w + c => w
    return out

def wrap(text, width):
    # Greedy wrapping: as many words as fit on each line, one space between
    # them; a word longer than the width is cut into pieces that fit.
    [] => lines
    "" => line
    for w in words(text):
        while len(w) > width:
            if line != "":
                lines + [line] => lines
                "" => line
            lines + [w[0:width]] => lines
            w[width:len(w)] => w
        if line == "":
            w => line
        elif len(line) + 1 + len(w) <= width:
            line + " " + w => line
        else:
            lines + [line] => lines
            w => line
    if line != "":
        lines + [line] => lines
    return lines

def aligned(line, width, how):
    # The line placed in the width: "left", "centre" (an odd space left
    # over goes on the right) or "right". Nothing is added after the text.
    0 => left
    if how == "right":
        width - len(line) => left
    elif how == "centre":
        int((width - len(line) - (width - len(line)) % 2) / 2) => left
    return " " * left + line

def boxed(lines, pad):
    # The corpus case's frame: as wide as the longest line plus the
    # padding on both sides, so the right edge closes exactly.
    0 => widest
    for line in lines:
        if len(line) > widest:
            len(line) => widest
    widest + pad * 2 => inner
    ["+" + "-" * inner + "+"] => out
    for line in lines:
        out + ["|" + " " * pad + line + " " * (inner - pad - len(line)) + "|"] => out
    return out + ["+" + "-" * inner + "+"]
Python projection (banner.py)
LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 !?.-"
SMALL = "abcdefghijklmnopqrstuvwxyz"
GLYPHS = [[" ### ", "#   #", "#####", "#   #", "#   #"], ["#### ", "#   #", "#### ", "#   #", "#### "], [" ####", "#    ", "#    ", "#    ", " ####"], ["#### ", "#   #", "#   #", "#   #", "#### "], ["#####", "#    ", "#### ", "#    ", "#####"], ["#####", "#    ", "#### ", "#    ", "#    "], [" ####", "#    ", "#  ##", "#   #", " ####"], ["#   #", "#   #", "#####", "#   #", "#   #"], ["#####", "  #  ", "  #  ", "  #  ", "#####"], ["#####", "   # ", "   # ", "#  # ", " ##  "], ["#   #", "#  # ", "###  ", "#  # ", "#   #"], ["#    ", "#    ", "#    ", "#    ", "#####"], ["#   #", "## ##", "# # #", "#   #", "#   #"], ["#   #", "##  #", "# # #", "#  ##", "#   #"], [" ### ", "#   #", "#   #", "#   #", " ### "], ["#### ", "#   #", "#### ", "#    ", "#    "], [" ### ", "#   #", "# # #", "#  # ", " ## #"], ["#### ", "#   #", "#### ", "#  # ", "#   #"], [" ####", "#    ", " ### ", "    #", "#### "], ["#####", "  #  ", "  #  ", "  #  ", "  #  "], ["#   #", "#   #", "#   #", "#   #", " ### "], ["#   #", "#   #", "#   #", " # # ", "  #  "], ["#   #", "#   #", "# # #", "## ##", "#   #"], ["#   #", " # # ", "  #  ", " # # ", "#   #"], ["#   #", " # # ", "  #  ", "  #  ", "  #  "], ["#####", "   # ", "  #  ", " #   ", "#####"], [" ### ", "#  ##", "# # #", "##  #", " ### "], ["  #  ", " ##  ", "  #  ", "  #  ", " ### "], [" ### ", "#   #", "  ## ", " #   ", "#####"], ["#### ", "    #", " ### ", "    #", "#### "], ["#   #", "#   #", "#####", "    #", "    #"], ["#####", "#    ", "#### ", "    #", "#### "], [" ### ", "#    ", "#### ", "#   #", " ### "], ["#####", "    #", "   # ", "  #  ", "  #  "], [" ### ", "#   #", " ### ", "#   #", " ### "], [" ### ", "#   #", " ####", "    #", " ### "], ["     ", "     ", "     ", "     ", "     "], ["  #  ", "  #  ", "  #  ", "     ", "  #  "], [" ### ", "#   #", "  ## ", "     ", "  #  "], ["     ", "     ", "     ", "     ", "  #  "], ["     ", "     ", "#####", "     ", "     "]]

def index_in(s, c):
    for i in range(0, len(s)):
        if s[i] == c:
            return i
    return -1

def upper(c):
    i = index_in(SMALL, c)
    if i >= 0:
        return LETTERS[i]
    return c

def can_draw(text):
    for c in text:
        if index_in(LETTERS, upper(c)) == -1:
            return False
    return True

def trim_right(s):
    j = len(s)
    while j > 0 and s[j - 1] == " ":
        j = j - 1
    return s[0:j]

def big(text):
    rows = ["", "", "", "", ""]
    for k in range(0, len(text)):
        g = GLYPHS[index_in(LETTERS, upper(text[k]))]
        for r in range(0, 5):
            if k > 0:
                rows[r] = rows[r] + " "
            rows[r] = rows[r] + g[r]
    for r in range(0, 5):
        rows[r] = trim_right(rows[r])
    return rows

def words(s):
    out = []
    w = ""
    for c in s + " ":
        if c == " ":
            if w != "":
                out = out + [w]
            w = ""
        else:
            w = w + c
    return out

def wrap(text, width):
    lines = []
    line = ""
    for w in words(text):
        while len(w) > width:
            if line != "":
                lines = lines + [line]
                line = ""
            lines = lines + [w[0:width]]
            w = w[width:len(w)]
        if line == "":
            line = w
        elif len(line) + 1 + len(w) <= width:
            line = line + " " + w
        else:
            lines = lines + [line]
            line = w
    if line != "":
        lines = lines + [line]
    return lines

def aligned(line, width, how):
    left = 0
    if how == "right":
        left = width - len(line)
    elif how == "centre":
        left = int((width - len(line) - (width - len(line)) % 2) / 2)
    return " " * left + line

def boxed(lines, pad):
    widest = 0
    for line in lines:
        if len(line) > widest:
            widest = len(line)
    inner = widest + pad * 2
    out = ["+" + "-" * inner + "+"]
    for line in lines:
        out = out + ["|" + " " * pad + line + " " * (inner - pad - len(line)) + "|"]
    return out + ["+" + "-" * inner + "+"]

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.