Project P038

Matrix calculator

Up to three matrices of at most 5 x 5, typed with whole numbers, fractions or decimals and kept as exact fractions: sum, difference, product, a number times a matrix, transpose, the determinant by Gaussian elimination and the inverse by Gauss-Jordan elimination - checked by multiplying back. Each result is kept as R for the next operation.

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

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

Up to three matrices, A, B and C, of at most 5 x 5. Entries are typed as whole numbers, fractions or decimals and kept as exact fractions, so no answer is rounded. One operation at a time - sum, difference, product, a number times a matrix, transpose, determinant, inverse - and each result is kept as R, which the next operation can use.

  • main.eml - the menu, entering a matrix row by row, reading an operation, and the matrices on screen
  • matrix.eml - the operations
  • frac.eml - exact fractions, from P007 (calculator), with reading and writing a number

How each part works:

  • A number is a fraction in lowest terms with a positive denominator. EML has no // and / goes through a float, so the whole-number division that keeps fractions in lowest terms is written out by doubling, as in P007.
  • The product takes row i of the first matrix times column j of the second for each entry, as in the corpus case matrix-multiplication; the transpose turns rows into columns as in matrix-transpose-manual.
  • The determinant is found by Gaussian elimination: multiples of one row are taken from the rows below it until the matrix is upper triangular, and the determinant is the product of the diagonal, its sign flipped for each swap of two rows. The corpus case matrix-determinant expands by cofactors, which takes n! terms - 120 for a 5 x 5 matrix - where elimination takes about n^3 / 3 steps; with exact fractions both give the same answer.
  • The inverse is found by Gauss-Jordan elimination on A beside the identity matrix: when the left half has become the identity, the right half is the inverse. A matrix whose determinant is 0 has none. Every inverse is multiplied back, and the program says so when A times R is the identity.
  • An operation is read as one of A+B, A-B, A*B, a number times a matrix (2*A, -3/2*B), trans A, det A and inv A; spaces do not matter, and R can stand in for a matrix.

What is checked: A, B or C as the matrix to enter; 1 to 5 rows and columns; each row as exactly that many numbers - whole numbers, fractions like 3/4 or decimals like 0.25, each part at most 9 digits, a denominator never 0; an operation the matrices fit: the same size for a sum or difference, as many columns in the first as rows in the second for a product, a square matrix for a determinant or an inverse. An empty answer cancels.

Sessions: sessions/basic.in enters a 3 x 3 matrix A, a 3 x 2 matrix B with fractions and decimals, and a 2 x 2 matrix C whose second row is twice its first; finds the determinant of A (-1), its inverse (checked), R times A (the identity), A times B, the transpose of B and 1/2 times C; then the determinant of C is 0 and C has no inverse. sessions/bad-input.in gives menu choices 0 and x, shows and calculates with no matrices, matrix D, sizes 0, 6 and x, rows with too few and too many numbers, x and 1/0, a matrix cancelled halfway; then a 2 x 3 matrix A and the identity B: a sum and a product of the wrong sizes, the determinant and inverse of a matrix that is not square, R before there is a result, C and Q, and three operations that are not one; and finally B times A, its transpose, 0.5 times R and -3/2 times B.

Built on the verified corpus cases matrix-multiplication (a product by row times column), matrix-determinant (a determinant by cofactor expansion, checked against known values) and matrix-transpose-manual (a transpose by nested loops).

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
== Matrix calculator ==
Matrices A, B and C of up to 5 x 5; entries are kept as exact fractions.

1) enter a matrix  2) show the matrices  3) calculate  4) quit
choice> 0
Pick a number from 1 to 4.

1) enter a matrix  2) show the matrices  3) calculate  4) quit
choice> x
Pick a number from 1 to 4.

1) enter a matrix  2) show the matrices  3) calculate  4) quit
choice> 2
No matrices yet.

1) enter a matrix  2) show the matrices  3) calculate  4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> det A
A has not been entered yet.

1) enter a matrix  2) show the matrices  3) calculate  4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> 
Cancelled.

1) enter a matrix  2) show the matrices  3) calculate  4) quit
choice> 1
which matrix (A, B or C)> D
Type A, B or C.
which matrix (A, B or C)> 
Cancelled.

1) enter a matrix  2) show the matrices  3) calculate  4) quit
choice> 1
which matrix (A, B or C)> A
rows (1 to 5)> 0
Type a number from 1 to 5.
rows (1 to 5)> 6
Type a number from 1 to 5.
rows (1 to 5)> x
Type a number from 1 to 5.
rows (1 to 5)> 
Cancelled.

1) enter a matrix  2) show the matrices  3) calculate  4) quit
choice> 1
which matrix (A, B or C)> A
rows (1 to 5)> 2
columns (1 to 5)> 3
row 1> 1 2
That is 2 numbers; this row needs 3.
row 1> 1 2 3 4
That is 4 numbers; this row needs 3.
row 1> 1 x 3
Type whole numbers, fractions like 3/4 or decimals like 0.25, separated by spaces.
row 1> 1/0 2 3
Type whole numbers, fractions like 3/4 or decimals like 0.25, separated by spaces.
row 1> 1 2 3
row 2> 
Cancelled.

1) enter a matrix  2) show the matrices  3) calculate  4) quit
choice> 1
which matrix (A, B or C)> A
rows (1 to 5)> 2
columns (1 to 5)> 3
row 1> 1 2 3
row 2> 4 5 6
A (2 x 3):
  [ 1 2 3 ]
  [ 4 5 6 ]

1) enter a matrix  2) show the matrices  3) calculate  4) quit
choice> 1
which matrix (A, B or C)> b
rows (1 to 5)> 2
columns (1 to 5)> 2
row 1> 1 0
row 2> 0 1
B (2 x 2):
  [ 1 0 ]
  [ 0 1 ]

1) enter a matrix  2) show the matrices  3) calculate  4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> A+B
A is 2 x 3 and B is 2 x 2: adding or subtracting needs the same size.

1) enter a matrix  2) show the matrices  3) calculate  4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> A*A
A is 2 x 3 and A is 2 x 3: a product needs the columns of the first (3) to equal the rows of the second (2).

1) enter a matrix  2) show the matrices  3) calculate  4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> det A
Only a square matrix has a determinant or an inverse; this one is 2 x 3.

1) enter a matrix  2) show the matrices  3) calculate  4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> inv A
Only a square matrix has a determinant or an inverse; this one is 2 x 3.

1) enter a matrix  2) show the matrices  3) calculate  4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> R*A
There is no result yet.

1) enter a matrix  2) show the matrices  3) calculate  4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> C+A
C has not been entered yet.

1) enter a matrix  2) show the matrices  3) calculate  4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> Q*A
There is no matrix called Q.

1) enter a matrix  2) show the matrices  3) calculate  4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> A
Type one operation, like A+B, A-B, A*B, 2*A, trans A, det A or inv A.

1) enter a matrix  2) show the matrices  3) calculate  4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> A+
Type one operation, like A+B, A-B, A*B, 2*A, trans A, det A or inv A.

1) enter a matrix  2) show the matrices  3) calculate  4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> +A
Type one operation, like A+B, A-B, A*B, 2*A, trans A, det A or inv A.

1) enter a matrix  2) show the matrices  3) calculate  4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> B*A
R is B * A.
R (2 x 3):
  [ 1 2 3 ]
  [ 4 5 6 ]

1) enter a matrix  2) show the matrices  3) calculate  4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> trans R
R is the transpose of R.
R (3 x 2):
  [ 1 4 ]
  [ 2 5 ]
  [ 3 6 ]

1) enter a matrix  2) show the matrices  3) calculate  4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> 0.5*R
R is 1/2 times R.
R (3 x 2):
  [ 1/2   2 ]
  [   1 5/2 ]
  [ 3/2   3 ]

1) enter a matrix  2) show the matrices  3) calculate  4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> -3/2*B
R is -3/2 times B.
R (2 x 2):
  [ -3/2    0 ]
  [    0 -3/2 ]

1) enter a matrix  2) show the matrices  3) calculate  4) quit
choice> 2
A (2 x 3):
  [ 1 2 3 ]
  [ 4 5 6 ]
B (2 x 2):
  [ 1 0 ]
  [ 0 1 ]
R (2 x 2):
  [ -3/2    0 ]
  [    0 -3/2 ]

1) enter a matrix  2) show the matrices  3) calculate  4) quit
choice> 4
Bye.
What was typed (68 lines)
0
x
2
3
det A
3

1
D

1
A
0
6
x

1
A
2
3
1 2
1 2 3 4
1 x 3
1/0 2 3
1 2 3

1
A
2
3
1 2 3
4 5 6
1
b
2
2
1 0
0 1
3
A+B
3
A*A
3
det A
3
inv A
3
R*A
3
C+A
3
Q*A
3
A
3
A+
3
+A
3
B*A
3
trans R
3
0.5*R
3
-3/2*B
2
4

basic

interpreter: byte-equal
== Matrix calculator ==
Matrices A, B and C of up to 5 x 5; entries are kept as exact fractions.

1) enter a matrix  2) show the matrices  3) calculate  4) quit
choice> 1
which matrix (A, B or C)> A
rows (1 to 5)> 3
columns (1 to 5)> 3
row 1> 2 1 -1
row 2> -3 -1 2
row 3> -2 1 2
A (3 x 3):
  [  2  1 -1 ]
  [ -3 -1  2 ]
  [ -2  1  2 ]

1) enter a matrix  2) show the matrices  3) calculate  4) quit
choice> 1
which matrix (A, B or C)> B
rows (1 to 5)> 3
columns (1 to 5)> 2
row 1> 1/2 0.25
row 2> -1 2
row 3> 3 -3/4
B (3 x 2):
  [ 1/2  1/4 ]
  [  -1    2 ]
  [   3 -3/4 ]

1) enter a matrix  2) show the matrices  3) calculate  4) quit
choice> 1
which matrix (A, B or C)> c
rows (1 to 5)> 2
columns (1 to 5)> 2
row 1> 1 2
row 2> 2, 4
C (2 x 2):
  [ 1 2 ]
  [ 2 4 ]

1) enter a matrix  2) show the matrices  3) calculate  4) quit
choice> 2
A (3 x 3):
  [  2  1 -1 ]
  [ -3 -1  2 ]
  [ -2  1  2 ]
B (3 x 2):
  [ 1/2  1/4 ]
  [  -1    2 ]
  [   3 -3/4 ]
C (2 x 2):
  [ 1 2 ]
  [ 2 4 ]

1) enter a matrix  2) show the matrices  3) calculate  4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> det A
The answer is the determinant of A: -1.

1) enter a matrix  2) show the matrices  3) calculate  4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> inv A
R is the inverse of A.
R (3 x 3):
  [  4  3 -1 ]
  [ -2 -2  1 ]
  [  5  4 -1 ]
Checked: A * R is the identity matrix.

1) enter a matrix  2) show the matrices  3) calculate  4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> R * A
R is R * A.
R (3 x 3):
  [ 1 0 0 ]
  [ 0 1 0 ]
  [ 0 0 1 ]

1) enter a matrix  2) show the matrices  3) calculate  4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> A*B
R is A * B.
R (3 x 2):
  [   -3  13/4 ]
  [ 11/2 -17/4 ]
  [    4     0 ]

1) enter a matrix  2) show the matrices  3) calculate  4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> trans B
R is the transpose of B.
R (2 x 3):
  [ 1/2 -1    3 ]
  [ 1/4  2 -3/4 ]

1) enter a matrix  2) show the matrices  3) calculate  4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> 1/2 * C
R is 1/2 times C.
R (2 x 2):
  [ 1/2 1 ]
  [   1 2 ]

1) enter a matrix  2) show the matrices  3) calculate  4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> det C
The answer is the determinant of C: 0.

1) enter a matrix  2) show the matrices  3) calculate  4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> inv C
C has no inverse: its determinant is 0.

1) enter a matrix  2) show the matrices  3) calculate  4) quit
choice> 4
Bye.
What was typed (38 lines)
1
A
3
3
2 1 -1
-3 -1 2
-2 1 2
1
B
3
2
1/2 0.25
-1 2
3 -3/4
1
c
2
2
1 2
2, 4
2
3
det A
3
inv A
3
R * A
3
A*B
3
trans B
3
1/2 * C
3
det C
3
inv C
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
# P038 matrix calculator: up to three matrices A, B and C of at most 5 x 5,
# with entries typed as whole numbers, fractions or decimals and kept as
# exact fractions. One operation at a time - sum, difference, product, a
# number times a matrix, transpose, determinant, inverse - and its result is
# kept as R, which the next operation can use.
import frac
import matrix

5 => largest

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 upper(s):
    "abcdefghijklmnopqrstuvwxyz" => small
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ" => big
    "" => out
    for c in s:
        0 => k
        while k < 26 and small[k] != c:
            k + 1 => k
        if k < 26:
            out + big[k] => out
        else:
            out + c => out
    return out

def words(s):
    # s cut 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 without_spaces(s):
    "" => out
    for c in s:
        if c != " ":
            out + c => out
    return out

def dims(m):
    return str(len(m)) + " x " + str(len(m[0]))

def show(name, m):
    # Columns right-aligned to their widest entry.
    (name + " (" + dims(m) + "):") ^0
    [] => widths
    for j in [0:len(m[0]) - 1]:
        0 => w
        for row in m:
            if len(frac.text(row[j])) > w:
                len(frac.text(row[j])) => w
        widths + [w] => widths
    for row in m:
        "  [" => line
        for j in [0:len(row) - 1]:
            frac.text(row[j]) => t
            while len(t) < widths[j]:
                " " + t => t
            line + " " + t => line
        (line + " ]") ^0

def ask_size(prompt):
    while True:
        trim(input(prompt)) => answer
        if answer == "":
            return -1
        frac.digits_value(answer) => n
        if n >= 1 and n <= largest:
            return n
        ("Type a number from 1 to " + str(largest) + ".") ^0

def ask_row(i, cols):
    # One row of cols numbers, or [] when the answer is empty.
    while True:
        trim(input("row " + str(i) + "> ")) => answer
        if answer == "":
            return []
        words(answer) => ws
        [] => row
        True => good
        for w in ws:
            frac.parse(w) => x
            if len(x) == 0:
                False => good
            else:
                row + [x] => row
        if not good:
            "Type whole numbers, fractions like 3/4 or decimals like 0.25, separated by spaces." ^0
        elif len(row) != cols:
            ("That is " + str(len(row)) + " numbers; this row needs " + str(cols) + ".") ^0
        else:
            return row

def enter(store):
    while True:
        upper(trim(input("which matrix (A, B or C)> "))) => name
        if name == "":
            "Cancelled." ^0
            return store
        if name == "A" or name == "B" or name == "C":
            ask_size("rows (1 to " + str(largest) + ")> ") => rows
            if rows == -1:
                "Cancelled." ^0
                return store
            ask_size("columns (1 to " + str(largest) + ")> ") => cols
            if cols == -1:
                "Cancelled." ^0
                return store
            [] => m
            for i in [1:rows]:
                ask_row(i, cols) => row
                if len(row) == 0:
                    "Cancelled." ^0
                    return store
                m + [row] => m
            m => store[name]
            show(name, m)
            return store
        "Type A, B or C." ^0

def operand(store, s):
    # The matrix called s, [False, why] if there is none.
    upper(s) => name
    if not (name in store):
        if name == "A" or name == "B" or name == "C":
            return [False, name + " has not been entered yet."]
        if name == "R":
            return [False, "There is no result yet."]
        return [False, "There is no matrix called " + s + "."]
    return [True, store[name]]

def calculate(store, s):
    # One operation. Returns [True, "matrix" or "number", the result, what
    # was done, a check to show or ""], or [False, why not].
    without_spaces(s) => s
    upper(s) => u
    if u[0:3] == "DET" or u[0:3] == "INV" or u[0:5] == "TRANS":
        3 => k
        if u[0:5] == "TRANS":
            5 => k
        operand(store, s[k:len(s)]) => x
        if not x[0]:
            return x
        x[1] => a
        upper(s[k:len(s)]) => name
        if u[0:5] == "TRANS":
            return [True, "matrix", matrix.transposed(a), "the transpose of " + name, ""]
        if len(a) != len(a[0]):
            return [False, "Only a square matrix has a determinant or an inverse; this one is " + dims(a) + "."]
        if u[0:3] == "DET":
            return [True, "number", matrix.determinant(a), "the determinant of " + name, ""]
        matrix.inverse(a) => inv
        if len(inv) == 0:
            return [False, name + " has no inverse: its determinant is 0."]
        "" => check
        if matrix.product(a, inv) == matrix.identity(len(a)):
            "Checked: " + name + " * R is the identity matrix." => check
        return [True, "matrix", inv, "the inverse of " + name, check]
    # a binary operation: * first, so that "-2*A" keeps its minus sign
    -1 => at
    for i in [0:len(s) - 1]:
        if at == -1 and s[i] == "*":
            i => at
    if at == -1:
        for i in [1:len(s) - 1]:
            if at == -1 and (s[i] == "+" or s[i] == "-"):
                i => at
    if at <= 0 or at == len(s) - 1:
        return [False, "Type one operation, like A+B, A-B, A*B, 2*A, trans A, det A or inv A."]
    s[at] => op
    s[0:at] => left
    s[at + 1:len(s)] => right
    operand(store, right) => y
    if not y[0]:
        return y
    y[1] => b
    if op == "*":
        frac.parse(left) => k
        if len(k) > 0:
            return [True, "matrix", matrix.scaled(k, b), frac.text(k) + " times " + upper(right), ""]
    operand(store, left) => x
    if not x[0]:
        return x
    x[1] => a
    upper(left) + " " + op + " " + upper(right) => what
    if op == "*":
        if len(a[0]) != len(b):
            return [False, upper(left) + " is " + dims(a) + " and " + upper(right) + " is " + dims(b) + ": a product needs the columns of the first (" + str(len(a[0])) + ") to equal the rows of the second (" + str(len(b)) + ")."]
        return [True, "matrix", matrix.product(a, b), what, ""]
    if len(a) != len(b) or len(a[0]) != len(b[0]):
        return [False, upper(left) + " is " + dims(a) + " and " + upper(right) + " is " + dims(b) + ": adding or subtracting needs the same size."]
    return [True, "matrix", matrix.combined(a, b, op == "-"), what, ""]

"== Matrix calculator ==" ^0
"Matrices A, B and C of up to 5 x 5; entries are kept as exact fractions." ^0
{} => store
True => running
while running:
    "" ^0
    "1) enter a matrix  2) show the matrices  3) calculate  4) quit" ^0
    trim(input("choice> ")) => choice
    if choice == "1":
        enter(store) => store
    elif choice == "2":
        if len(store) == 0:
            "No matrices yet." ^0
        for name in ["A", "B", "C", "R"]:
            if name in store:
                show(name, store[name])
    elif choice == "3":
        trim(input("operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> ")) => op
        if op == "":
            "Cancelled." ^0
        else:
            calculate(store, op) => r
            if not r[0]:
                r[1] ^0
            elif r[1] == "number":
                ("The answer is " + r[3] + ": " + frac.text(r[2]) + ".") ^0
            else:
                r[2] => store["R"]
                ("R is " + r[3] + ".") ^0
                show("R", r[2])
                if r[4] != "":
                    r[4] ^0
    elif choice == "4":
        False => running
    else:
        "Pick a number from 1 to 4." ^0
"Bye." ^0
Python projection (main.py)
import frac
import matrix
largest = 5

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 upper(s):
    small = "abcdefghijklmnopqrstuvwxyz"
    big = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    out = ""
    for c in s:
        k = 0
        while k < 26 and small[k] != c:
            k = k + 1
        if k < 26:
            out = out + big[k]
        else:
            out = out + c
    return out

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 without_spaces(s):
    out = ""
    for c in s:
        if c != " ":
            out = out + c
    return out

def dims(m):
    return str(len(m)) + " x " + str(len(m[0]))

def show(name, m):
    print(name + " (" + dims(m) + "):")
    widths = []
    for j in range(0, len(m[0])):
        w = 0
        for row in m:
            if len(frac.text(row[j])) > w:
                w = len(frac.text(row[j]))
        widths = widths + [w]
    for row in m:
        line = "  ["
        for j in range(0, len(row)):
            t = frac.text(row[j])
            while len(t) < widths[j]:
                t = " " + t
            line = line + " " + t
        print(line + " ]")

def ask_size(prompt):
    while True:
        answer = trim(input(prompt))
        if answer == "":
            return -1
        n = frac.digits_value(answer)
        if n >= 1 and n <= largest:
            return n
        print("Type a number from 1 to " + str(largest) + ".")

def ask_row(i, cols):
    while True:
        answer = trim(input("row " + str(i) + "> "))
        if answer == "":
            return []
        ws = words(answer)
        row = []
        good = True
        for w in ws:
            x = frac.parse(w)
            if len(x) == 0:
                good = False
            else:
                row = row + [x]
        if not good:
            print("Type whole numbers, fractions like 3/4 or decimals like 0.25, separated by spaces.")
        elif len(row) != cols:
            print("That is " + str(len(row)) + " numbers; this row needs " + str(cols) + ".")
        else:
            return row

def enter(store):
    while True:
        name = upper(trim(input("which matrix (A, B or C)> ")))
        if name == "":
            print("Cancelled.")
            return store
        if name == "A" or name == "B" or name == "C":
            rows = ask_size("rows (1 to " + str(largest) + ")> ")
            if rows == -1:
                print("Cancelled.")
                return store
            cols = ask_size("columns (1 to " + str(largest) + ")> ")
            if cols == -1:
                print("Cancelled.")
                return store
            m = []
            for i in range(1, rows+1):
                row = ask_row(i, cols)
                if len(row) == 0:
                    print("Cancelled.")
                    return store
                m = m + [row]
            store[name] = m
            show(name, m)
            return store
        print("Type A, B or C.")

def operand(store, s):
    name = upper(s)
    if not name in store:
        if name == "A" or name == "B" or name == "C":
            return [False, name + " has not been entered yet."]
        if name == "R":
            return [False, "There is no result yet."]
        return [False, "There is no matrix called " + s + "."]
    return [True, store[name]]

def calculate(store, s):
    s = without_spaces(s)
    u = upper(s)
    if u[0:3] == "DET" or u[0:3] == "INV" or u[0:5] == "TRANS":
        k = 3
        if u[0:5] == "TRANS":
            k = 5
        x = operand(store, s[k:len(s)])
        if not x[0]:
            return x
        a = x[1]
        name = upper(s[k:len(s)])
        if u[0:5] == "TRANS":
            return [True, "matrix", matrix.transposed(a), "the transpose of " + name, ""]
        if len(a) != len(a[0]):
            return [False, "Only a square matrix has a determinant or an inverse; this one is " + dims(a) + "."]
        if u[0:3] == "DET":
            return [True, "number", matrix.determinant(a), "the determinant of " + name, ""]
        inv = matrix.inverse(a)
        if len(inv) == 0:
            return [False, name + " has no inverse: its determinant is 0."]
        check = ""
        if matrix.product(a, inv) == matrix.identity(len(a)):
            check = "Checked: " + name + " * R is the identity matrix."
        return [True, "matrix", inv, "the inverse of " + name, check]
    at = -1
    for i in range(0, len(s)):
        if at == -1 and s[i] == "*":
            at = i
    if at == -1:
        for i in range(1, len(s)):
            if at == -1 and (s[i] == "+" or s[i] == "-"):
                at = i
    if at <= 0 or at == len(s) - 1:
        return [False, "Type one operation, like A+B, A-B, A*B, 2*A, trans A, det A or inv A."]
    op = s[at]
    left = s[0:at]
    right = s[at + 1:len(s)]
    y = operand(store, right)
    if not y[0]:
        return y
    b = y[1]
    if op == "*":
        k = frac.parse(left)
        if len(k) > 0:
            return [True, "matrix", matrix.scaled(k, b), frac.text(k) + " times " + upper(right), ""]
    x = operand(store, left)
    if not x[0]:
        return x
    a = x[1]
    what = upper(left) + " " + op + " " + upper(right)
    if op == "*":
        if len(a[0]) != len(b):
            return [False, upper(left) + " is " + dims(a) + " and " + upper(right) + " is " + dims(b) + ": a product needs the columns of the first (" + str(len(a[0])) + ") to equal the rows of the second (" + str(len(b)) + ")."]
        return [True, "matrix", matrix.product(a, b), what, ""]
    if len(a) != len(b) or len(a[0]) != len(b[0]):
        return [False, upper(left) + " is " + dims(a) + " and " + upper(right) + " is " + dims(b) + ": adding or subtracting needs the same size."]
    return [True, "matrix", matrix.combined(a, b, op == "-"), what, ""]

print("== Matrix calculator ==")
print("Matrices A, B and C of up to 5 x 5; entries are kept as exact fractions.")
store = {}
running = True
while running:
    print("")
    print("1) enter a matrix  2) show the matrices  3) calculate  4) quit")
    choice = trim(input("choice> "))
    if choice == "1":
        store = enter(store)
    elif choice == "2":
        if len(store) == 0:
            print("No matrices yet.")
        for name in ["A", "B", "C", "R"]:
            if name in store:
                show(name, store[name])
    elif choice == "3":
        op = trim(input("operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> "))
        if op == "":
            print("Cancelled.")
        else:
            r = calculate(store, op)
            if not r[0]:
                print(r[1])
            elif r[1] == "number":
                print("The answer is " + r[3] + ": " + frac.text(r[2]) + ".")
            else:
                store["R"] = r[2]
                print("R is " + r[3] + ".")
                show("R", r[2])
                if r[4] != "":
                    print(r[4])
    elif choice == "4":
        running = False
    else:
        print("Pick a number from 1 to 4.")
print("Bye.")

matrix.eml

eml
# P038 matrix calculator - the operations. A matrix is a list of rows of
# fractions (see frac.eml), all rows the same length.
import frac

def size(m):
    return [len(m), len(m[0])]

def zero(rows, cols):
    [] => m
    for i in [1:rows]:
        [] => row
        for j in [1:cols]:
            row + [[0, 1]] => row
        m + [row] => m
    return m

def identity(n):
    zero(n, n) => m
    for i in [0:n - 1]:
        [1, 1] => m[i][i]
    return m

def combined(a, b, subtract):
    # a + b, or a - b when subtract is True; the sizes are equal.
    [] => out
    for i in [0:len(a) - 1]:
        [] => row
        for j in [0:len(a[0]) - 1]:
            if subtract:
                row + [frac.minus(a[i][j], b[i][j])] => row
            else:
                row + [frac.plus(a[i][j], b[i][j])] => row
        out + [row] => out
    return out

def product(a, b):
    # a * b: entry (i, j) is row i of a times column j of b, as in the corpus
    # case matrix-multiplication; columns of a = rows of b.
    [] => out
    for i in [0:len(a) - 1]:
        [] => row
        for j in [0:len(b[0]) - 1]:
            [0, 1] => total
            for k in [0:len(b) - 1]:
                frac.plus(total, frac.times(a[i][k], b[k][j])) => total
            row + [total] => row
        out + [row] => out
    return out

def scaled(k, a):
    [] => out
    for row in a:
        [] => r
        for x in row:
            r + [frac.times(k, x)] => r
        out + [r] => out
    return out

def transposed(a):
    [] => out
    for j in [0:len(a[0]) - 1]:
        [] => row
        for i in [0:len(a) - 1]:
            row + [a[i][j]] => row
        out + [row] => out
    return out

def copied(a):
    [] => out
    for row in a:
        out + [row[0:len(row)]] => out
    return out

def determinant(a):
    # Gaussian elimination with exact fractions: bring the matrix to upper
    # triangular form by taking multiples of one row from the rows below it;
    # the determinant is the product of the diagonal, with its sign flipped
    # for each swap of two rows. About n^3 / 3 steps, where expanding by
    # cofactors (the corpus case matrix-determinant) takes n! terms.
    copied(a) => m
    len(m) => n
    [1, 1] => det
    for col in [0:n - 1]:
        col => p
        while p < n and frac.is_zero(m[p][col]):
            p + 1 => p
        if p == n:
            return [0, 1]
        if p != col:
            m[p] => keep
            m[col] => m[p]
            keep => m[col]
            frac.minus([0, 1], det) => det
        frac.times(det, m[col][col]) => det
        for r in [col + 1:n - 1]:
            if not frac.is_zero(m[r][col]):
                frac.over(m[r][col], m[col][col]) => f
                for j in [col:n - 1]:
                    frac.minus(m[r][j], frac.times(f, m[col][j])) => m[r][j]
    return det

def inverse(a):
    # Gauss-Jordan elimination on [a | I]: when the left half has become I,
    # the right half is the inverse. [] if a is singular.
    len(a) => n
    [] => m
    identity(n) => e
    for i in [0:n - 1]:
        m + [a[i] + e[i]] => m
    for col in [0:n - 1]:
        col => p
        while p < n and frac.is_zero(m[p][col]):
            p + 1 => p
        if p == n:
            return []
        if p != col:
            m[p] => keep
            m[col] => m[p]
            keep => m[col]
        m[col][col] => pivot
        [] => row
        for x in m[col]:
            row + [frac.over(x, pivot)] => row
        row => m[col]
        for r in [0:n - 1]:
            if r != col and not frac.is_zero(m[r][col]):
                m[r][col] => f
                [] => changed
                for j in [0:2 * n - 1]:
                    changed + [frac.minus(m[r][j], frac.times(f, m[col][j]))] => changed
                changed => m[r]
    [] => out
    for i in [0:n - 1]:
        out + [m[i][n:2 * n]] => out
    return out
Python projection (matrix.py)
import frac

def size(m):
    return [len(m), len(m[0])]

def zero(rows, cols):
    m = []
    for i in range(1, rows+1):
        row = []
        for j in range(1, cols+1):
            row = row + [[0, 1]]
        m = m + [row]
    return m

def identity(n):
    m = zero(n, n)
    for i in range(0, n):
        m[i][i] = [1, 1]
    return m

def combined(a, b, subtract):
    out = []
    for i in range(0, len(a)):
        row = []
        for j in range(0, len(a[0])):
            if subtract:
                row = row + [frac.minus(a[i][j], b[i][j])]
            else:
                row = row + [frac.plus(a[i][j], b[i][j])]
        out = out + [row]
    return out

def product(a, b):
    out = []
    for i in range(0, len(a)):
        row = []
        for j in range(0, len(b[0])):
            total = [0, 1]
            for k in range(0, len(b)):
                total = frac.plus(total, frac.times(a[i][k], b[k][j]))
            row = row + [total]
        out = out + [row]
    return out

def scaled(k, a):
    out = []
    for row in a:
        r = []
        for x in row:
            r = r + [frac.times(k, x)]
        out = out + [r]
    return out

def transposed(a):
    out = []
    for j in range(0, len(a[0])):
        row = []
        for i in range(0, len(a)):
            row = row + [a[i][j]]
        out = out + [row]
    return out

def copied(a):
    out = []
    for row in a:
        out = out + [row[0:len(row)]]
    return out

def determinant(a):
    m = copied(a)
    n = len(m)
    det = [1, 1]
    for col in range(0, n):
        p = col
        while p < n and frac.is_zero(m[p][col]):
            p = p + 1
        if p == n:
            return [0, 1]
        if p != col:
            keep = m[p]
            m[p] = m[col]
            m[col] = keep
            det = frac.minus([0, 1], det)
        det = frac.times(det, m[col][col])
        for r in range(col + 1, n):
            if not frac.is_zero(m[r][col]):
                f = frac.over(m[r][col], m[col][col])
                for j in range(col, n):
                    m[r][j] = frac.minus(m[r][j], frac.times(f, m[col][j]))
    return det

def inverse(a):
    n = len(a)
    m = []
    e = identity(n)
    for i in range(0, n):
        m = m + [a[i] + e[i]]
    for col in range(0, n):
        p = col
        while p < n and frac.is_zero(m[p][col]):
            p = p + 1
        if p == n:
            return []
        if p != col:
            keep = m[p]
            m[p] = m[col]
            m[col] = keep
        pivot = m[col][col]
        row = []
        for x in m[col]:
            row = row + [frac.over(x, pivot)]
        m[col] = row
        for r in range(0, n):
            if r != col and not frac.is_zero(m[r][col]):
                f = m[r][col]
                changed = []
                for j in range(0, 2 * n):
                    changed = changed + [frac.minus(m[r][j], frac.times(f, m[col][j]))]
                m[r] = changed
    out = []
    for i in range(0, n):
        out = out + [m[i][n:2 * n]]
    return out

frac.eml

eml
# P038 matrix calculator - exact fractions, from P007 (calculator). A number
# is a list [n, d]: n / d in lowest terms with d > 0. Integers have no size
# limit, but EML has no //, and a / b goes through a float that cannot hold a
# large quotient exactly, so whole-number division is written out below.

def quotient(a, b):
    # a // b for whole numbers a >= 0 and b > 0, exact at any size. Long
    # division by doubling: take away the largest b * 2^k that still fits.
    0 => q
    while a >= b:
        b => m
        1 => k
        while m + m <= a:
            m + m => m
            k + k => k
        a - m => a
        q + k => q
    return q

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

def make(n, d):
    # n / d in lowest terms with a positive denominator (d != 0).
    if d < 0:
        0 - n => n
        0 - d => d
    abs(n) => a
    gcd(a, d) => g
    if n < 0:
        return [0 - quotient(a, g), quotient(d, g)]
    return [quotient(a, g), quotient(d, g)]

def plus(x, y):
    return make(x[0] * y[1] + y[0] * x[1], x[1] * y[1])

def minus(x, y):
    return make(x[0] * y[1] - y[0] * x[1], x[1] * y[1])

def times(x, y):
    return make(x[0] * y[0], x[1] * y[1])

def over(x, y):
    # x / y; the caller has checked that y is not zero.
    return make(x[0] * y[1], x[1] * y[0])

def is_zero(x):
    return x[0] == 0

def digits_value(s):
    # The value of a string of 1 to 9 digits, otherwise -1.
    if s == "" or len(s) > 9:
        return -1
    0 => n
    for c in s:
        if not (c in "0123456789"):
            return -1
        n * 10 + int(c) => n
    return n

def parse(s):
    # "3", "-2", "3/4", "-0.25" as a fraction; [] if it is not a number.
    1 => sign
    if len(s) > 0 and s[0] == "-":
        -1 => sign
        s[1:len(s)] => s
    0 => k
    while k < len(s) and s[k] != "/" and s[k] != ".":
        k + 1 => k
    if k == len(s):
        digits_value(s) => n
        if n < 0:
            return []
        return make(sign * n, 1)
    digits_value(s[0:k]) => whole
    s[k + 1:len(s)] => rest
    digits_value(rest) => part
    if whole < 0 or part < 0:
        return []
    if s[k] == "/":
        if part == 0:
            return []
        return make(sign * whole, part)
    # a decimal point: 0.25 is 25 / 100
    1 => scale
    for c in rest:
        scale * 10 => scale
    return make(sign * (whole * scale + part), scale)

def text(x):
    # "3", "-3", "3/4", "-3/4".
    if x[1] == 1:
        return str(x[0])
    return str(x[0]) + "/" + str(x[1])
Python projection (frac.py)
def quotient(a, b):
    q = 0
    while a >= b:
        m = b
        k = 1
        while m + m <= a:
            m = m + m
            k = k + k
        a = a - m
        q = q + k
    return q

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

def make(n, d):
    if d < 0:
        n = 0 - n
        d = 0 - d
    a = abs(n)
    g = gcd(a, d)
    if n < 0:
        return [0 - quotient(a, g), quotient(d, g)]
    return [quotient(a, g), quotient(d, g)]

def plus(x, y):
    return make(x[0] * y[1] + y[0] * x[1], x[1] * y[1])

def minus(x, y):
    return make(x[0] * y[1] - y[0] * x[1], x[1] * y[1])

def times(x, y):
    return make(x[0] * y[0], x[1] * y[1])

def over(x, y):
    return make(x[0] * y[1], x[1] * y[0])

def is_zero(x):
    return x[0] == 0

def digits_value(s):
    if s == "" or len(s) > 9:
        return -1
    n = 0
    for c in s:
        if not c in "0123456789":
            return -1
        n = n * 10 + int(c)
    return n

def parse(s):
    sign = 1
    if len(s) > 0 and s[0] == "-":
        sign = -1
        s = s[1:len(s)]
    k = 0
    while k < len(s) and s[k] != "/" and s[k] != ".":
        k = k + 1
    if k == len(s):
        n = digits_value(s)
        if n < 0:
            return []
        return make(sign * n, 1)
    whole = digits_value(s[0:k])
    rest = s[k + 1:len(s)]
    part = digits_value(rest)
    if whole < 0 or part < 0:
        return []
    if s[k] == "/":
        if part == 0:
            return []
        return make(sign * whole, part)
    scale = 1
    for c in rest:
        scale = scale * 10
    return make(sign * (whole * scale + part), scale)

def text(x):
    if x[1] == 1:
        return str(x[0])
    return str(x[0]) + "/" + str(x[1])

Built on these corpus cases