Project P023

Number toolkit

Whole-number tools from a text menu: is a number prime (and if not, its smallest factor), its prime factors with powers, the gcd and lcm of several numbers with Euclid's steps for two, the primes in a range by a sieve, a number's divisors - perfect, abundant or deficient - and the perfect numbers up to a limit.

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

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

Whole-number tools from a text menu: whether a number is prime (and if not, its smallest factor), its prime factors with powers, the gcd and lcm of two to ten numbers - with Euclid's steps for two - the primes in a range, a number's divisors and whether it is perfect, abundant or deficient, and the perfect numbers up to a limit. Numbers go up to 10^12.

  • main.eml - the menu and its questions, with their checks, and what the screen shows
  • primes.eml - smallest factor, prime factors, Euclid's algorithm, gcd and the sieve
  • divisors.eml - divisors, the perfect/abundant/deficient test, and perfect numbers up to a limit
  • text.eml - trimming, whole numbers, words and joining

How each part works:

  • Prime test and factors: trial division by 2 and then by odd numbers, only while the divisor's square does not pass the number - at most a million steps for 10^12, with every product exact. 600851475143 = 71 x 839 x 1471 x 6857.
  • gcd and lcm: Euclid's algorithm, folded over all the numbers; the lcm of two is a / gcd x b. For two numbers each step a = q x b + r is shown.
  • Primes in a range: the sieve of Eratosthenes up to the end of the range (at most 100,000), striking out the multiples of each prime from its square; a range covers at most 1000 numbers.
  • Divisors: each d whose square does not pass n gives both d and n / d. A number is perfect when its proper divisors add up to it (28 = 1 + 2 + 4 + 7 + 14), abundant when to more, deficient when to less.
  • Perfect numbers up to a limit (at most 10,000): every d is added to the divisor sums of its multiples 2d, 3d, and so on - no division at all.

The lists the sieve and the divisor sums use are made in one step ([True] * (n + 1)) rather than grown an item at a time, which would copy the list on every step.

What is checked: a number is whole and in range for its question (0 to 10^12 for the prime test, 2 to 10^12 for factors, 1 to 10^12 otherwise); the gcd takes two to ten numbers from 1 to 10^12; a range ends at or after its start and covers at most 1000 numbers up to 100,000; the perfect-number limit is 1 to 10,000. Anything else asks again; nothing cancels.

Sessions: sessions/basic.in tests 1000003 (prime) and 1000001 (101 x 9901), factors 360, 600851475143 and 97, finds the gcd and lcm of 84 and 36 with Euclid's steps and of 12, 18 and 30, lists the 25 primes up to 100, shows the divisors of 28 (perfect) and 12 (abundant), and the perfect numbers up to 10,000; sessions/bad-input.in types numbers that are words, negative or too large, 0 and 1, gcd input with one number, a word, a zero or eleven numbers, ranges that run backwards, are too wide, hold one prime or none, the divisors of 1, and a limit with no perfect number below it.

Built on the verified corpus cases prime-checker (trial division up to the square root), prime-factorization (dividing out each factor in turn), gcd-lcm-calculator (Euclid's algorithm, and the lcm from the gcd) and perfect-number-checker (adding up the proper divisors).

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

== Number toolkit ==
1) is it prime  2) prime factors  3) gcd and lcm  4) primes in a range
5) divisors  6) perfect numbers  7) quit
choice> 0
Pick a number from 1 to 7.

== Number toolkit ==
1) is it prime  2) prime factors  3) gcd and lcm  4) primes in a range
5) divisors  6) perfect numbers  7) quit
choice> 1
number> abc
Type a whole number from 0 to 10^12, or nothing to cancel.
number> -5
Type a whole number from 0 to 10^12, or nothing to cancel.
number> 1000000000001
Type a whole number from 0 to 10^12, or nothing to cancel.
number> 1
1 is neither prime nor composite.

== Number toolkit ==
1) is it prime  2) prime factors  3) gcd and lcm  4) primes in a range
5) divisors  6) perfect numbers  7) quit
choice> 1
number> 0
0 is neither prime nor composite.

== Number toolkit ==
1) is it prime  2) prime factors  3) gcd and lcm  4) primes in a range
5) divisors  6) perfect numbers  7) quit
choice> 2
number> 1
Type a whole number from 2 to 10^12, or nothing to cancel.
number> 
Cancelled.

== Number toolkit ==
1) is it prime  2) prime factors  3) gcd and lcm  4) primes in a range
5) divisors  6) perfect numbers  7) quit
choice> 3
numbers (2 to 10, with spaces)> 84
Type two to ten whole numbers from 1 to 10^12, separated by spaces.
numbers (2 to 10, with spaces)> 84 x
Type two to ten whole numbers from 1 to 10^12, separated by spaces.
numbers (2 to 10, with spaces)> 0 5
Type two to ten whole numbers from 1 to 10^12, separated by spaces.
numbers (2 to 10, with spaces)> 1 2 3 4 5 6 7 8 9 10 11
Type two to ten whole numbers from 1 to 10^12, separated by spaces.
numbers (2 to 10, with spaces)> 
Cancelled.

== Number toolkit ==
1) is it prime  2) prime factors  3) gcd and lcm  4) primes in a range
5) divisors  6) perfect numbers  7) quit
choice> 4
from> 50
to> 40
The range has to end at or after its start.
to> 1200
A range covers at most 1000 numbers.
to> 54
1 prime from 50 to 54:
  53

== Number toolkit ==
1) is it prime  2) prime factors  3) gcd and lcm  4) primes in a range
5) divisors  6) perfect numbers  7) quit
choice> 4
from> 24
to> 28
No primes from 24 to 28.

== Number toolkit ==
1) is it prime  2) prime factors  3) gcd and lcm  4) primes in a range
5) divisors  6) perfect numbers  7) quit
choice> 5
number> 1
1 has 1 divisor:
  1
Its proper divisors add up to 0, so it is deficient.

== Number toolkit ==
1) is it prime  2) prime factors  3) gcd and lcm  4) primes in a range
5) divisors  6) perfect numbers  7) quit
choice> 6
up to> 5
No perfect numbers up to 5.

== Number toolkit ==
1) is it prime  2) prime factors  3) gcd and lcm  4) primes in a range
5) divisors  6) perfect numbers  7) quit
choice> 7
Bye.
What was typed (30 lines)
0
1
abc
-5
1000000000001
1
1
0
2
1

3
84
84 x
0 5
1 2 3 4 5 6 7 8 9 10 11

4
50
40
1200
54
4
24
28
5
1
6
5
7

basic

interpreter: byte-equal

== Number toolkit ==
1) is it prime  2) prime factors  3) gcd and lcm  4) primes in a range
5) divisors  6) perfect numbers  7) quit
choice> 1
number> 1000003
1000003 is prime.

== Number toolkit ==
1) is it prime  2) prime factors  3) gcd and lcm  4) primes in a range
5) divisors  6) perfect numbers  7) quit
choice> 1
number> 1000001
1000001 is not prime; its smallest factor is 101.

== Number toolkit ==
1) is it prime  2) prime factors  3) gcd and lcm  4) primes in a range
5) divisors  6) perfect numbers  7) quit
choice> 2
number> 360
360 = 2^3 x 3^2 x 5

== Number toolkit ==
1) is it prime  2) prime factors  3) gcd and lcm  4) primes in a range
5) divisors  6) perfect numbers  7) quit
choice> 2
number> 600851475143
600851475143 = 71 x 839 x 1471 x 6857

== Number toolkit ==
1) is it prime  2) prime factors  3) gcd and lcm  4) primes in a range
5) divisors  6) perfect numbers  7) quit
choice> 2
number> 97
97 is prime.

== Number toolkit ==
1) is it prime  2) prime factors  3) gcd and lcm  4) primes in a range
5) divisors  6) perfect numbers  7) quit
choice> 3
numbers (2 to 10, with spaces)> 84 36
  84 = 2 x 36 + 12
  36 = 3 x 12 + 0
gcd = 12, lcm = 252

== Number toolkit ==
1) is it prime  2) prime factors  3) gcd and lcm  4) primes in a range
5) divisors  6) perfect numbers  7) quit
choice> 3
numbers (2 to 10, with spaces)> 12, 18, 30
gcd = 6, lcm = 180

== Number toolkit ==
1) is it prime  2) prime factors  3) gcd and lcm  4) primes in a range
5) divisors  6) perfect numbers  7) quit
choice> 4
from> 1
to> 100
25 primes from 1 to 100:
  2, 3, 5, 7, 11, 13, 17, 19, 23, 29
  31, 37, 41, 43, 47, 53, 59, 61, 67, 71
  73, 79, 83, 89, 97

== Number toolkit ==
1) is it prime  2) prime factors  3) gcd and lcm  4) primes in a range
5) divisors  6) perfect numbers  7) quit
choice> 5
number> 28
28 has 6 divisors:
  1, 2, 4, 7, 14, 28
Its proper divisors add up to 28, so it is perfect.

== Number toolkit ==
1) is it prime  2) prime factors  3) gcd and lcm  4) primes in a range
5) divisors  6) perfect numbers  7) quit
choice> 5
number> 12
12 has 6 divisors:
  1, 2, 3, 4, 6, 12
Its proper divisors add up to 16, so it is abundant.

== Number toolkit ==
1) is it prime  2) prime factors  3) gcd and lcm  4) primes in a range
5) divisors  6) perfect numbers  7) quit
choice> 6
up to> 10000
Perfect numbers up to 10000: 6, 28, 496, 8128

== Number toolkit ==
1) is it prime  2) prime factors  3) gcd and lcm  4) primes in a range
5) divisors  6) perfect numbers  7) quit
choice> 7
Bye.
What was typed (24 lines)
1
1000003
1
1000001
2
360
2
600851475143
2
97
3
84 36
3
12, 18, 30
4
1
100
5
28
5
12
6
10000
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
# P023 number toolkit: primes, prime factors, gcd and lcm, primes in a range,
# divisors, and perfect numbers - whole numbers up to 10^12.
import primes
import divisors
import text

1000000000000 => biggest
100000 => sieve_top
1000 => widest_range
10000 => perfect_top

def ask_number(prompt, low, high, message):
    # A whole number from low to high, asked again until one is typed; -1 if
    # the answer is empty, which cancels.
    while True:
        text.trim(input(prompt)) => answer
        if answer == "":
            return 0 - 1
        text.number(answer) => n
        if n >= low and n <= high:
            return n
        message ^0

def plural(n, word):
    if n == 1:
        return "1 " + word
    return str(n) + " " + word + "s"

def factor_text(fs):
    # [[2, 3], [3, 2], [5, 1]] as "2^3 x 3^2 x 5".
    [] => parts
    for f in fs:
        if f[1] == 1:
            parts + [str(f[0])] => parts
        else:
            parts + [str(f[0]) + "^" + str(f[1])] => parts
    return text.joined(parts, " x ")

def in_lines(items, per_line):
    # The items in rows of per_line, each row indented.
    [] => lines
    0 => i
    while i < len(items):
        lines + ["  " + text.joined(items[i:i + per_line], ", ")] => lines
        i + per_line => i
    return lines

"Whole numbers from 1 to 1,000,000,000,000 (10^12)." => number_message
True => running
while running:
    "" ^0
    "== Number toolkit ==" ^0
    "1) is it prime  2) prime factors  3) gcd and lcm  4) primes in a range" ^0
    "5) divisors  6) perfect numbers  7) quit" ^0
    text.trim(input("choice> ")) => choice
    if choice == "1":
        ask_number("number> ", 0, biggest, "Type a whole number from 0 to 10^12, or nothing to cancel.") => n
        if n == 0 - 1:
            "Cancelled." ^0
        elif n < 2:
            (str(n) + " is neither prime nor composite.") ^0
        elif primes.smallest_factor(n) == n:
            (str(n) + " is prime.") ^0
        else:
            (str(n) + " is not prime; its smallest factor is " + str(primes.smallest_factor(n)) + ".") ^0
    elif choice == "2":
        ask_number("number> ", 2, biggest, "Type a whole number from 2 to 10^12, or nothing to cancel.") => n
        if n == 0 - 1:
            "Cancelled." ^0
        else:
            primes.factors(n) => fs
            if len(fs) == 1 and fs[0][1] == 1:
                (str(n) + " is prime.") ^0
            else:
                (str(n) + " = " + factor_text(fs)) ^0
    elif choice == "3":
        None => nums
        while nums == None:
            text.trim(input("numbers (2 to 10, with spaces)> ")) => answer
            if answer == "":
                break
            text.words(answer) => ws
            [] => got
            for w in ws:
                text.number(w) => v
                if v >= 1 and v <= biggest:
                    got + [v] => got
            if len(ws) >= 2 and len(ws) <= 10 and len(got) == len(ws):
                got => nums
            else:
                "Type two to ten whole numbers from 1 to 10^12, separated by spaces." ^0
        if nums == None:
            "Cancelled." ^0
        else:
            nums[0] => g
            nums[0] => l
            for v in nums[1:len(nums)]:
                primes.gcd(g, v) => g
                primes.div(l, primes.gcd(l, v)) * v => l
            if len(nums) == 2:
                for s in primes.euclid(nums[0], nums[1]):
                    ("  " + str(s[0]) + " = " + str(s[1]) + " x " + str(s[2]) + " + " + str(s[3])) ^0
            ("gcd = " + str(g) + ", lcm = " + str(l)) ^0
    elif choice == "4":
        ask_number("from> ", 1, sieve_top, "Type a whole number from 1 to 100000, or nothing to cancel.") => low
        0 - 1 => high
        while low != 0 - 1 and high == 0 - 1:
            ask_number("to> ", 1, sieve_top, "Type a whole number from 1 to 100000, or nothing to cancel.") => high
            if high == 0 - 1:
                0 - 1 => low
            elif high < low:
                "The range has to end at or after its start." ^0
                0 - 1 => high
            elif high - low >= widest_range:
                ("A range covers at most " + str(widest_range) + " numbers.") ^0
                0 - 1 => high
        if low == 0 - 1:
            "Cancelled." ^0
        else:
            primes.sieve(low, high) => ps
            if len(ps) == 0:
                ("No primes from " + str(low) + " to " + str(high) + ".") ^0
            else:
                (plural(len(ps), "prime") + " from " + str(low) + " to " + str(high) + ":") ^0
                for line in in_lines(ps, 10):
                    line ^0
    elif choice == "5":
        ask_number("number> ", 1, biggest, "Type a whole number from 1 to 10^12, or nothing to cancel.") => n
        if n == 0 - 1:
            "Cancelled." ^0
        else:
            divisors.divisors(n) => ds
            divisors.kind(n) => k
            (str(n) + " has " + plural(len(ds), "divisor") + ":") ^0
            for line in in_lines(ds, 10):
                line ^0
            ("Its proper divisors add up to " + str(k[0]) + ", so it is " + k[1] + ".") ^0
    elif choice == "6":
        ask_number("up to> ", 1, perfect_top, "Type a whole number from 1 to 10000, or nothing to cancel.") => top
        if top == 0 - 1:
            "Cancelled." ^0
        else:
            divisors.perfect_up_to(top) => ps
            if len(ps) == 0:
                ("No perfect numbers up to " + str(top) + ".") ^0
            else:
                ("Perfect numbers up to " + str(top) + ": " + text.joined(ps, ", ")) ^0
    elif choice == "7":
        False => running
    else:
        "Pick a number from 1 to 7." ^0
"Bye." ^0
Python projection (main.py)
import primes
import divisors
import text
biggest = 1000000000000
sieve_top = 100000
widest_range = 1000
perfect_top = 10000

def ask_number(prompt, low, high, message):
    while True:
        answer = text.trim(input(prompt))
        if answer == "":
            return 0 - 1
        n = text.number(answer)
        if n >= low and n <= high:
            return n
        print(message)

def plural(n, word):
    if n == 1:
        return "1 " + word
    return str(n) + " " + word + "s"

def factor_text(fs):
    parts = []
    for f in fs:
        if f[1] == 1:
            parts = parts + [str(f[0])]
        else:
            parts = parts + [str(f[0]) + "^" + str(f[1])]
    return text.joined(parts, " x ")

def in_lines(items, per_line):
    lines = []
    i = 0
    while i < len(items):
        lines = lines + ["  " + text.joined(items[i:i + per_line], ", ")]
        i = i + per_line
    return lines

number_message = "Whole numbers from 1 to 1,000,000,000,000 (10^12)."
running = True
while running:
    print("")
    print("== Number toolkit ==")
    print("1) is it prime  2) prime factors  3) gcd and lcm  4) primes in a range")
    print("5) divisors  6) perfect numbers  7) quit")
    choice = text.trim(input("choice> "))
    if choice == "1":
        n = ask_number("number> ", 0, biggest, "Type a whole number from 0 to 10^12, or nothing to cancel.")
        if n == 0 - 1:
            print("Cancelled.")
        elif n < 2:
            print(str(n) + " is neither prime nor composite.")
        elif primes.smallest_factor(n) == n:
            print(str(n) + " is prime.")
        else:
            print(str(n) + " is not prime; its smallest factor is " + str(primes.smallest_factor(n)) + ".")
    elif choice == "2":
        n = ask_number("number> ", 2, biggest, "Type a whole number from 2 to 10^12, or nothing to cancel.")
        if n == 0 - 1:
            print("Cancelled.")
        else:
            fs = primes.factors(n)
            if len(fs) == 1 and fs[0][1] == 1:
                print(str(n) + " is prime.")
            else:
                print(str(n) + " = " + factor_text(fs))
    elif choice == "3":
        nums = None
        while nums == None:
            answer = text.trim(input("numbers (2 to 10, with spaces)> "))
            if answer == "":
                break
            ws = text.words(answer)
            got = []
            for w in ws:
                v = text.number(w)
                if v >= 1 and v <= biggest:
                    got = got + [v]
            if len(ws) >= 2 and len(ws) <= 10 and len(got) == len(ws):
                nums = got
            else:
                print("Type two to ten whole numbers from 1 to 10^12, separated by spaces.")
        if nums == None:
            print("Cancelled.")
        else:
            g = nums[0]
            l = nums[0]
            for v in nums[1:len(nums)]:
                g = primes.gcd(g, v)
                l = primes.div(l, primes.gcd(l, v)) * v
            if len(nums) == 2:
                for s in primes.euclid(nums[0], nums[1]):
                    print("  " + str(s[0]) + " = " + str(s[1]) + " x " + str(s[2]) + " + " + str(s[3]))
            print("gcd = " + str(g) + ", lcm = " + str(l))
    elif choice == "4":
        low = ask_number("from> ", 1, sieve_top, "Type a whole number from 1 to 100000, or nothing to cancel.")
        high = 0 - 1
        while low != 0 - 1 and high == 0 - 1:
            high = ask_number("to> ", 1, sieve_top, "Type a whole number from 1 to 100000, or nothing to cancel.")
            if high == 0 - 1:
                low = 0 - 1
            elif high < low:
                print("The range has to end at or after its start.")
                high = 0 - 1
            elif high - low >= widest_range:
                print("A range covers at most " + str(widest_range) + " numbers.")
                high = 0 - 1
        if low == 0 - 1:
            print("Cancelled.")
        else:
            ps = primes.sieve(low, high)
            if len(ps) == 0:
                print("No primes from " + str(low) + " to " + str(high) + ".")
            else:
                print(plural(len(ps), "prime") + " from " + str(low) + " to " + str(high) + ":")
                for line in in_lines(ps, 10):
                    print(line)
    elif choice == "5":
        n = ask_number("number> ", 1, biggest, "Type a whole number from 1 to 10^12, or nothing to cancel.")
        if n == 0 - 1:
            print("Cancelled.")
        else:
            ds = divisors.divisors(n)
            k = divisors.kind(n)
            print(str(n) + " has " + plural(len(ds), "divisor") + ":")
            for line in in_lines(ds, 10):
                print(line)
            print("Its proper divisors add up to " + str(k[0]) + ", so it is " + k[1] + ".")
    elif choice == "6":
        top = ask_number("up to> ", 1, perfect_top, "Type a whole number from 1 to 10000, or nothing to cancel.")
        if top == 0 - 1:
            print("Cancelled.")
        else:
            ps = divisors.perfect_up_to(top)
            if len(ps) == 0:
                print("No perfect numbers up to " + str(top) + ".")
            else:
                print("Perfect numbers up to " + str(top) + ": " + text.joined(ps, ", "))
    elif choice == "7":
        running = False
    else:
        print("Pick a number from 1 to 7.")
print("Bye.")

primes.eml

eml
# P023 number toolkit - primes, factors, gcd and lcm. Numbers go up to 10^12,
# so trial division stops by 10^6 and every product stays exact.

def div(a, b):
    # a // b for whole numbers a >= 0 and b > 0.
    return int((a - a % b) / b)

def smallest_factor(n):
    # The smallest factor of n >= 2 that is more than 1 (n itself if prime):
    # 2, then the odd numbers whose square does not pass n.
    if n % 2 == 0:
        return 2
    3 => d
    while d * d <= n:
        if n % d == 0:
            return d
        d + 2 => d
    return n

def factors(n):
    # The prime factors of n >= 2 as [prime, power] pairs, smallest first.
    [] => out
    2 => d
    while d * d <= n:
        if n % d == 0:
            0 => power
            while n % d == 0:
                div(n, d) => n
                power + 1 => power
            out + [[d, power]] => out
        if d == 2:
            3 => d
        else:
            d + 2 => d
    if n > 1:
        out + [[n, 1]] => out
    return out

def euclid(a, b):
    # The steps of Euclid's algorithm for a and b, as [a, q, b, r] with
    # a = q x b + r, down to the step whose remainder is 0.
    [] => steps
    while b > 0:
        a % b => r
        steps + [[a, div(a, b), b, r]] => steps
        b => a
        r => b
    return steps

def gcd(a, b):
    while b > 0:
        a % b => r
        b => a
        r => b
    return a

def sieve(low, high):
    # The primes from low to high (high <= 100000), by the sieve of
    # Eratosthenes: strike out every multiple of each prime from its square.
    # One flag per number, made at once: growing a list one item at a time
    # copies it every time.
    [True] * (high + 1) => is_prime
    False => is_prime[0]
    if high >= 1:
        False => is_prime[1]
    2 => p
    while p * p <= high:
        if is_prime[p]:
            p * p => m
            while m <= high:
                False => is_prime[m]
                m + p => m
        p + 1 => p
    [] => out
    for i in [low:high]:
        if is_prime[i]:
            out + [i] => out
    return out
Python projection (primes.py)
def div(a, b):
    return int((a - a % b) / b)

def smallest_factor(n):
    if n % 2 == 0:
        return 2
    d = 3
    while d * d <= n:
        if n % d == 0:
            return d
        d = d + 2
    return n

def factors(n):
    out = []
    d = 2
    while d * d <= n:
        if n % d == 0:
            power = 0
            while n % d == 0:
                n = div(n, d)
                power = power + 1
            out = out + [[d, power]]
        if d == 2:
            d = 3
        else:
            d = d + 2
    if n > 1:
        out = out + [[n, 1]]
    return out

def euclid(a, b):
    steps = []
    while b > 0:
        r = a % b
        steps = steps + [[a, div(a, b), b, r]]
        a = b
        b = r
    return steps

def gcd(a, b):
    while b > 0:
        r = a % b
        a = b
        b = r
    return a

def sieve(low, high):
    is_prime = [True] * (high + 1)
    is_prime[0] = False
    if high >= 1:
        is_prime[1] = False
    p = 2
    while p * p <= high:
        if is_prime[p]:
            m = p * p
            while m <= high:
                is_prime[m] = False
                m = m + p
        p = p + 1
    out = []
    for i in range(low, high+1):
        if is_prime[i]:
            out = out + [i]
    return out

divisors.eml

eml
# P023 number toolkit - divisors, and perfect, abundant and deficient numbers.
# A number is perfect when its proper divisors (all but itself) add up to it,
# abundant when they add up to more, deficient when to less.
import primes

def divisors(n):
    # The divisors of n >= 1 in order: each d up to the square root gives d
    # and n / d.
    [] => small
    [] => large
    1 => d
    while d * d <= n:
        if n % d == 0:
            small + [d] => small
            if d * d != n:
                [primes.div(n, d)] + large => large
        d + 1 => d
    return small + large

def kind(n):
    # [sum of proper divisors, "perfect" | "abundant" | "deficient"].
    0 => s
    for d in divisors(n):
        if d != n:
            s + d => s
    if s == n:
        return [s, "perfect"]
    if s > n:
        return [s, "abundant"]
    return [s, "deficient"]

def perfect_up_to(limit):
    # The perfect numbers up to limit, by adding every d to the proper-divisor
    # sums of its multiples 2d, 3d, ... (no division needed).
    [0] * (limit + 1) => sums
    1 => d
    while d + d <= limit:
        d + d => m
        while m <= limit:
            sums[m] + d => sums[m]
            m + d => m
        d + 1 => d
    [] => out
    for n in [2:limit]:
        if sums[n] == n:
            out + [n] => out
    return out
Python projection (divisors.py)
import primes

def divisors(n):
    small = []
    large = []
    d = 1
    while d * d <= n:
        if n % d == 0:
            small = small + [d]
            if d * d != n:
                large = [primes.div(n, d)] + large
        d = d + 1
    return small + large

def kind(n):
    s = 0
    for d in divisors(n):
        if d != n:
            s = s + d
    if s == n:
        return [s, "perfect"]
    if s > n:
        return [s, "abundant"]
    return [s, "deficient"]

def perfect_up_to(limit):
    sums = [0] * (limit + 1)
    d = 1
    while d + d <= limit:
        m = d + d
        while m <= limit:
            sums[m] = sums[m] + d
            m = m + d
        d = d + 1
    out = []
    for n in range(2, limit+1):
        if sums[n] == n:
            out = out + [n]
    return out

text.eml

eml
# P023 number toolkit - reading what is typed. The interpreter that checks
# every session does not run string methods yet, so the text handling is
# written out here.

def trim(s):
    # s without the spaces at either end.
    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):
    # The value of s if it is digits only (at least one, at most 13), otherwise
    # -1.
    if s == "" or len(s) > 13:
        return 0 - 1
    0 => n
    for c in s:
        if not (c in "0123456789"):
            return 0 - 1
        n * 10 + int(c) => n
    return n

def words(s):
    # The words of s, split at spaces and commas.
    [] => out
    "" => word
    for c in s + " ":
        if c == " " or c == ",":
            if word != "":
                out + [word] => out
            "" => word
        else:
            word + c => word
    return out

def joined(items, sep):
    "" => out
    for i in [0:len(items) - 1]:
        if i > 0:
            out + sep => out
        out + str(items[i]) => out
    return out
Python projection (text.py)
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 s == "" or len(s) > 13:
        return 0 - 1
    n = 0
    for c in s:
        if not c in "0123456789":
            return 0 - 1
        n = n * 10 + int(c)
    return n

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

def joined(items, sep):
    out = ""
    for i in range(0, len(items)):
        if i > 0:
            out = out + sep
        out = out + str(items[i])
    return out

Built on these corpus cases