Project P121

Attendance register

A mark for each student at each session - present, late, excused, absent or none - and attendance rates under four ways of counting, which flag different students: an absence, a missing mark and no sessions yet are three different nothings.

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

Every screen below was recorded under CPython. When this page was built, the EML interpreter replayed each session from the same input and printed the same bytes.

About

A mark for each student at each session: present, late, excused or absent - or no mark at all, when the register was not taken or the student had not joined yet. Four ways of counting give four attendance rates, and they flag different students.

  • main.eml - the menu, the register, the rates and who is below 80%, taking a register, correcting a mark, and a new student
  • roll.eml - the register, the four rates, percentages to a tenth, the flags, the sessions not taken, and the changes

How each part works:

  • A rate is present or late over one of four counts of sessions: 1. every session held; 2. the sessions since the student joined; 3. the sessions with a mark; 4. those, leaving out the excused ones. Percentages are rounded half up to a tenth, in whole numbers only.
  • As in the corpus case absent-vs-empty-vs-zero, an absence, a missing mark and a student with no sessions yet are three different things: - counting (1) or (2), a session nobody marked lowers everyone, and (1) counts Dana's sessions before joining as absences: 50.0% against 100.0%; - a new student with no marked session has no rate at all; (1) would give 0.0% and flag them.
  • The counts fall from (1) to (4) and the rates never drop, so each list of flagged students contains the next; the program names those flagged only when a session with no mark counts as an absence.
  • As in the corpus case attendance-register, the counts are a dict updated one mark at a time.

What is checked: menu choices 1 to 6; one mark per student, each P, L, E or A in either case, or skip; a student on the register, a session from the one they joined to the last, and a mark or none; a new name of up to 10 letters, not already there, and at most 8 students. An empty answer cancels.

Sessions: sessions/basic.in gives: - the register (session 3 not taken, Dana joining at session 4) and the rates: Ana and Dana flagged only when no mark counts as an absence; - Finn added, with no rate yet, and session 7 taken for all six; - Ana's session 3 corrected to P, and the rates; Cara's session 6 to no mark, and the register and the rates.

sessions/bad-input.in gives: - menu choices 0 and x; - registers: a cancel, two marks, an X, and PP; - corrections: a cancel, Zed, then Ana with a cancel, 0, 9 and x for the session, Dana's session 2 (before joining), and Ana's session 1 with a cancel, Q and PP; - new students: a cancel, Ana, Jo-Jo and Bartholomews, then Gia, Hal and Ivy, and Jon, one too many; - session 7 not taken, the register and the rates.

Built on the verified corpus cases attendance-register (a dict of counts kept across many small updates) and absent-vs-empty-vs-zero (three kinds of nothing, and a rule that treats them alike).

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
== Attendance register ==
A mark for each student at each session: present, late, excused or absent - or no mark, when the register was not taken or the student had not joined.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 0
Pick a number from 1 to 6.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> x
Pick a number from 1 to 6.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 3
Session 7: a mark for each of Ana, Ben, Cara, Dana and Eli in turn (P, L, E or A), or skip if the register was not taken.
marks> 
Cancelled.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 3
Session 7: a mark for each of Ana, Ben, Cara, Dana and Eli in turn (P, L, E or A), or skip if the register was not taken.
marks> P P
Type 5 marks, one for each student.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 3
Session 7: a mark for each of Ana, Ben, Cara, Dana and Eli in turn (P, L, E or A), or skip if the register was not taken.
marks> P X P P P
Each mark is P, L, E or A, separated by spaces.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 3
Session 7: a mark for each of Ana, Ben, Cara, Dana and Eli in turn (P, L, E or A), or skip if the register was not taken.
marks> PP P P P P
Each mark is P, L, E or A, separated by spaces.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 4
student> 
Cancelled.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 4
student> Zed
No such student.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 4
student> Ana
session (1 to 6)> 
Cancelled.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 4
student> Ana
session (1 to 6)> 0
Type a session from 1 to 6.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 4
student> Ana
session (1 to 6)> 9
Type a session from 1 to 6.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 4
student> Ana
session (1 to 6)> x
Type a session from 1 to 6.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 4
student> Dana
session (1 to 6)> 2
Dana joined at session 4.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 4
student> Ana
session (1 to 6)> 1
mark (P, L, E, A or none)> 
Cancelled.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 4
student> Ana
session (1 to 6)> 1
mark (P, L, E, A or none)> Q
Type P, L, E, A or none.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 4
student> Ana
session (1 to 6)> 1
mark (P, L, E, A or none)> PP
Type P, L, E, A or none.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 5
name (up to 10 letters)> 
Cancelled.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 5
name (up to 10 letters)> Ana
Ana is already on the register.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 5
name (up to 10 letters)> Jo-Jo
Type a name of up to 10 letters.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 5
name (up to 10 letters)> Bartholomews
Type a name of up to 10 letters.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 5
name (up to 10 letters)> Gia
Gia joins from session 7.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 5
name (up to 10 letters)> Hal
Hal joins from session 7.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 5
name (up to 10 letters)> Ivy
Ivy joins from session 7.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 5
name (up to 10 letters)> Jon
8 students is the most.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 3
Session 7: a mark for each of Ana, Ben, Cara, Dana, Eli, Gia, Hal and Ivy in turn (P, L, E or A), or skip if the register was not taken.
marks> skip
Session 7 recorded.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 1
Register (P present, L late, E excused, A absent, - no mark):
  session     1  2  3  4  5  6  7
  Ana         P  P  -  L  P  A  -
  Ben         P  A  -  P  E  P  -
  Cara        P  P  -  P  P  L  -
  Dana        -  -  -  P  P  P  -   joined at session 4
  Eli         A  L  -  E  E  A  -
  Gia         -  -  -  -  -  -  -   joined at session 7
  Hal         -  -  -  -  -  -  -   joined at session 7
  Ivy         -  -  -  -  -  -  -   joined at session 7
Sessions 3 and 7: the register was not taken.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 2
Present or late, out of: (1) every session held, (2) the sessions since joining, (3) the sessions with a mark, (4) those without excused ones.
  student          (1)       (2)       (3)       (4)
  Ana            57.1%     57.1%     80.0%     80.0%
  Ben            42.9%     42.9%     60.0%     75.0%
  Cara           71.4%     71.4%    100.0%    100.0%
  Dana           42.9%     75.0%    100.0%    100.0%
  Eli            14.3%     14.3%     20.0%     33.3%
  Gia             0.0%      0.0%         -         -
  Hal             0.0%      0.0%         -         -
  Ivy             0.0%      0.0%         -         -
Below 80%:
  (1) Ana, Ben, Cara, Dana, Eli, Gia, Hal and Ivy
  (2) Ana, Ben, Cara, Dana, Eli, Gia, Hal and Ivy
  (3) Ben and Eli
  (4) Ben and Eli
Flagged only when a session with no mark counts as an absence: Ana, Cara, Dana, Gia, Hal and Ivy.
No marked session yet, so no rate: Gia, Hal and Ivy. Counting every session held gives 0.0% instead.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 6
Bye.
What was typed (62 lines)
0
x
3

3
P P
3
P X P P P
3
PP P P P P
4

4
Zed
4
Ana

4
Ana
0
4
Ana
9
4
Ana
x
4
Dana
2
4
Ana
1

4
Ana
1
Q
4
Ana
1
PP
5

5
Ana
5
Jo-Jo
5
Bartholomews
5
Gia
5
Hal
5
Ivy
5
Jon
3
skip
1
2
6

basic

interpreter: byte-equal
== Attendance register ==
A mark for each student at each session: present, late, excused or absent - or no mark, when the register was not taken or the student had not joined.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 1
Register (P present, L late, E excused, A absent, - no mark):
  session     1  2  3  4  5  6
  Ana         P  P  -  L  P  A
  Ben         P  A  -  P  E  P
  Cara        P  P  -  P  P  L
  Dana        -  -  -  P  P  P   joined at session 4
  Eli         A  L  -  E  E  A
Session 3: the register was not taken.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 2
Present or late, out of: (1) every session held, (2) the sessions since joining, (3) the sessions with a mark, (4) those without excused ones.
  student          (1)       (2)       (3)       (4)
  Ana            66.7%     66.7%     80.0%     80.0%
  Ben            50.0%     50.0%     60.0%     75.0%
  Cara           83.3%     83.3%    100.0%    100.0%
  Dana           50.0%    100.0%    100.0%    100.0%
  Eli            16.7%     16.7%     20.0%     33.3%
Below 80%:
  (1) Ana, Ben, Dana and Eli
  (2) Ana, Ben and Eli
  (3) Ben and Eli
  (4) Ben and Eli
Flagged only when a session with no mark counts as an absence: Ana and Dana.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 5
name (up to 10 letters)> Finn
Finn joins from session 7.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 2
Present or late, out of: (1) every session held, (2) the sessions since joining, (3) the sessions with a mark, (4) those without excused ones.
  student          (1)       (2)       (3)       (4)
  Ana            66.7%     66.7%     80.0%     80.0%
  Ben            50.0%     50.0%     60.0%     75.0%
  Cara           83.3%     83.3%    100.0%    100.0%
  Dana           50.0%    100.0%    100.0%    100.0%
  Eli            16.7%     16.7%     20.0%     33.3%
  Finn            0.0%         -         -         -
Below 80%:
  (1) Ana, Ben, Dana, Eli and Finn
  (2) Ana, Ben and Eli
  (3) Ben and Eli
  (4) Ben and Eli
Flagged only when a session with no mark counts as an absence: Ana, Dana and Finn.
No marked session yet, so no rate: Finn. Counting every session held gives 0.0% instead.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 3
Session 7: a mark for each of Ana, Ben, Cara, Dana, Eli and Finn in turn (P, L, E or A), or skip if the register was not taken.
marks> P P A E L P
Session 7 recorded.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 1
Register (P present, L late, E excused, A absent, - no mark):
  session     1  2  3  4  5  6  7
  Ana         P  P  -  L  P  A  P
  Ben         P  A  -  P  E  P  P
  Cara        P  P  -  P  P  L  A
  Dana        -  -  -  P  P  P  E   joined at session 4
  Eli         A  L  -  E  E  A  L
  Finn        -  -  -  -  -  -  P   joined at session 7
Session 3: the register was not taken.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 2
Present or late, out of: (1) every session held, (2) the sessions since joining, (3) the sessions with a mark, (4) those without excused ones.
  student          (1)       (2)       (3)       (4)
  Ana            71.4%     71.4%     83.3%     83.3%
  Ben            57.1%     57.1%     66.7%     80.0%
  Cara           71.4%     71.4%     83.3%     83.3%
  Dana           42.9%     75.0%     75.0%    100.0%
  Eli            28.6%     28.6%     33.3%     50.0%
  Finn           14.3%    100.0%    100.0%    100.0%
Below 80%:
  (1) Ana, Ben, Cara, Dana, Eli and Finn
  (2) Ana, Ben, Cara, Dana and Eli
  (3) Ben, Dana and Eli
  (4) Eli
Flagged only when a session with no mark counts as an absence: Ana, Cara and Finn.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 4
student> Ana
session (1 to 7)> 3
mark (P, L, E, A or none)> P
Ana, session 3: - -> P.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 2
Present or late, out of: (1) every session held, (2) the sessions since joining, (3) the sessions with a mark, (4) those without excused ones.
  student          (1)       (2)       (3)       (4)
  Ana            85.7%     85.7%     85.7%     85.7%
  Ben            57.1%     57.1%     66.7%     80.0%
  Cara           71.4%     71.4%     83.3%     83.3%
  Dana           42.9%     75.0%     75.0%    100.0%
  Eli            28.6%     28.6%     33.3%     50.0%
  Finn           14.3%    100.0%    100.0%    100.0%
Below 80%:
  (1) Ben, Cara, Dana, Eli and Finn
  (2) Ben, Cara, Dana and Eli
  (3) Ben, Dana and Eli
  (4) Eli
Flagged only when a session with no mark counts as an absence: Cara and Finn.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 4
student> Cara
session (1 to 7)> 6
mark (P, L, E, A or none)> none
Cara, session 6: L -> -.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 1
Register (P present, L late, E excused, A absent, - no mark):
  session     1  2  3  4  5  6  7
  Ana         P  P  P  L  P  A  P
  Ben         P  A  -  P  E  P  P
  Cara        P  P  -  P  P  -  A
  Dana        -  -  -  P  P  P  E   joined at session 4
  Eli         A  L  -  E  E  A  L
  Finn        -  -  -  -  -  -  P   joined at session 7

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 2
Present or late, out of: (1) every session held, (2) the sessions since joining, (3) the sessions with a mark, (4) those without excused ones.
  student          (1)       (2)       (3)       (4)
  Ana            85.7%     85.7%     85.7%     85.7%
  Ben            57.1%     57.1%     66.7%     80.0%
  Cara           57.1%     57.1%     80.0%     80.0%
  Dana           42.9%     75.0%     75.0%    100.0%
  Eli            28.6%     28.6%     33.3%     50.0%
  Finn           14.3%    100.0%    100.0%    100.0%
Below 80%:
  (1) Ben, Cara, Dana, Eli and Finn
  (2) Ben, Cara, Dana and Eli
  (3) Ben, Dana and Eli
  (4) Eli
Flagged only when a session with no mark counts as an absence: Cara and Finn.

1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit
choice> 6
Bye.
What was typed (21 lines)
1
2
5
Finn
2
3
P P A E L P
1
2
4
Ana
3
P
2
4
Cara
6
none
1
2
6

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
# P121 attendance register: the register, attendance rates under four
# ways of counting, taking a register, correcting a mark, a new student.
import roll

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

def lpad(s, n):
    s => out
    while len(out) < n:
        " " + out => out
    return out

def names_text(ns):
    "" => out
    for i in [0:len(ns) - 1]:
        if i > 0 and i == len(ns) - 1:
            out + " and " => out
        elif i > 0:
            out + ", " => out
        out + ns[i] => out
    return out

def numbers_text(ns):
    [] => out
    for x in ns:
        out + [str(x)] => out
    return names_text(out)

def mark_of(c):
    # a typed mark as P, L, E or A, or "" if it is none of them
    if c == "P" or c == "p":
        return "P"
    if c == "L" or c == "l":
        return "L"
    if c == "E" or c == "e":
        return "E"
    if c == "A" or c == "a":
        return "A"
    return ""

def find(reg, name):
    for i in [0:len(reg) - 1]:
        if reg[i][0] == name:
            return i
    return 0 - 1

def show(reg):
    roll.held(reg) => n
    "Register (P present, L late, E excused, A absent, - no mark):" ^0
    "  session   " => head
    for k in [1:n]:
        head + lpad(str(k), 3) => head
    head ^0
    for st in reg:
        "  " + pad(st[0], 10) => line
        for m in st[2]:
            line + "  " + m => line
        if st[1] > 1:
            line + "   joined at session " + str(st[1]) => line
        line ^0
    roll.not_taken(reg) => gone
    if len(gone) == 1:
        ("Session " + str(gone[0]) + ": the register was not taken.") ^0
    elif len(gone) > 1:
        ("Sessions " + numbers_text(gone) + ": the register was not taken.") ^0

def rates(reg, line):
    roll.held(reg) => n
    ("Present or late, out of: (1) every session held, (2) the sessions since joining, (3) the sessions with a mark, (4) those without excused ones.") ^0
    "  " + pad("student", 10) => head
    for k in [1:4]:
        head + lpad("(" + str(k) + ")", 10) => head
    head ^0
    for st in reg:
        "  " + pad(st[0], 10) => out
        for p in roll.policies():
            roll.rate(st, p, n) => r
            out + lpad(roll.percent_text(r[0], r[1]), 10) => out
        out ^0
    ("Below " + str(line) + "%:") ^0
    [] => lists
    for k in [0:3]:
        roll.flagged(reg, roll.policies()[k], line) => f
        lists + [f] => lists
        if len(f) == 0:
            ("  (" + str(k + 1) + ") nobody") ^0
        else:
            ("  (" + str(k + 1) + ") " + names_text(f)) ^0
    [] => only
    for name in lists[0]:
        False => in3
        for x in lists[2]:
            if x == name:
                True => in3
        if not in3:
            only + [name] => only
    if len(only) > 0:
        ("Flagged only when a session with no mark counts as an absence: " + names_text(only) + ".") ^0
    [] => none
    for st in reg:
        if roll.rate(st, "marked", n)[1] == 0:
            none + [st[0]] => none
    if len(none) > 0:
        ("No marked session yet, so no rate: " + names_text(none) + ". Counting every session held gives 0.0% instead.") ^0

def take_register(reg):
    [] => names
    for st in reg:
        names + [st[0]] => names
    ("Session " + str(roll.held(reg) + 1) + ": a mark for each of " + names_text(names) + " in turn (P, L, E or A), or skip if the register was not taken.") ^0
    plain(input("marks> ")) => t
    if t == "":
        "Cancelled." ^0
        return reg
    [] => marks
    if t == "skip":
        for st in reg:
            marks + ["-"] => marks
    else:
        "" => w
        for c in t + " ":
            if c == " ":
                if w != "":
                    if len(w) != 1 or mark_of(w) == "":
                        "Each mark is P, L, E or A, separated by spaces." ^0
                        return reg
                    marks + [mark_of(w)] => marks
                "" => w
            else:
                w + c => w
        if len(marks) != len(reg):
            ("Type " + str(len(reg)) + " marks, one for each student.") ^0
            return reg
    roll.take(reg, marks) => out
    ("Session " + str(roll.held(out)) + " recorded.") ^0
    return out

def correct_mark(reg):
    plain(input("student> ")) => name
    if name == "":
        "Cancelled." ^0
        return reg
    find(reg, name) => i
    if i < 0:
        "No such student." ^0
        return reg
    plain(input("session (1 to " + str(roll.held(reg)) + ")> ")) => t
    if t == "":
        "Cancelled." ^0
        return reg
    0 => k
    if len(t) > 0 and len(t) <= 2:
        True => digits
        for c in t:
            if c < "0" or c > "9":
                False => digits
        if digits:
            int(t) => k
    if k < 1 or k > roll.held(reg):
        ("Type a session from 1 to " + str(roll.held(reg)) + ".") ^0
        return reg
    if k < reg[i][1]:
        (name + " joined at session " + str(reg[i][1]) + ".") ^0
        return reg
    plain(input("mark (P, L, E, A or none)> ")) => m
    if m == "":
        "Cancelled." ^0
        return reg
    "-" => mark
    if m != "none":
        if len(m) != 1 or mark_of(m) == "":
            "Type P, L, E, A or none." ^0
            return reg
        mark_of(m) => mark
    reg[i][2][k - 1] => before
    roll.correct(reg, i, k, mark) => out
    (name + ", session " + str(k) + ": " + before + " -> " + mark + ".") ^0
    return out

def add(reg):
    plain(input("name (up to 10 letters)> ")) => name
    if name == "":
        "Cancelled." ^0
        return reg
    if len(name) > 10:
        "Type a name of up to 10 letters." ^0
        return reg
    for c in name:
        if not ((c >= "a" and c <= "z") or (c >= "A" and c <= "Z")):
            "Type a name of up to 10 letters." ^0
            return reg
    if find(reg, name) >= 0:
        (name + " is already on the register.") ^0
        return reg
    if len(reg) >= 8:
        "8 students is the most." ^0
        return reg
    roll.add_student(reg, name) => out
    (name + " joins from session " + str(roll.held(out) + 1) + ".") ^0
    return out

roll.default_register() => reg
80 => line

"== Attendance register ==" ^0
"A mark for each student at each session: present, late, excused or absent - or no mark, when the register was not taken or the student had not joined." ^0
True => running
while running:
    "" ^0
    "1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit" ^0
    plain(input("choice> ")) => choice
    if choice == "1":
        show(reg)
    elif choice == "2":
        rates(reg, line)
    elif choice == "3":
        take_register(reg) => reg
    elif choice == "4":
        correct_mark(reg) => reg
    elif choice == "5":
        add(reg) => reg
    elif choice == "6":
        False => running
    else:
        "Pick a number from 1 to 6." ^0
"Bye." ^0
Python projection (main.py)
import roll

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

def lpad(s, n):
    out = s
    while len(out) < n:
        out = " " + out
    return out

def names_text(ns):
    out = ""
    for i in range(0, len(ns)):
        if i > 0 and i == len(ns) - 1:
            out = out + " and "
        elif i > 0:
            out = out + ", "
        out = out + ns[i]
    return out

def numbers_text(ns):
    out = []
    for x in ns:
        out = out + [str(x)]
    return names_text(out)

def mark_of(c):
    if c == "P" or c == "p":
        return "P"
    if c == "L" or c == "l":
        return "L"
    if c == "E" or c == "e":
        return "E"
    if c == "A" or c == "a":
        return "A"
    return ""

def find(reg, name):
    for i in range(0, len(reg)):
        if reg[i][0] == name:
            return i
    return 0 - 1

def show(reg):
    n = roll.held(reg)
    print("Register (P present, L late, E excused, A absent, - no mark):")
    head = "  session   "
    for k in range(1, n+1):
        head = head + lpad(str(k), 3)
    print(head)
    for st in reg:
        line = "  " + pad(st[0], 10)
        for m in st[2]:
            line = line + "  " + m
        if st[1] > 1:
            line = line + "   joined at session " + str(st[1])
        print(line)
    gone = roll.not_taken(reg)
    if len(gone) == 1:
        print("Session " + str(gone[0]) + ": the register was not taken.")
    elif len(gone) > 1:
        print("Sessions " + numbers_text(gone) + ": the register was not taken.")

def rates(reg, line):
    n = roll.held(reg)
    print("Present or late, out of: (1) every session held, (2) the sessions since joining, (3) the sessions with a mark, (4) those without excused ones.")
    head = "  " + pad("student", 10)
    for k in range(1, 5):
        head = head + lpad("(" + str(k) + ")", 10)
    print(head)
    for st in reg:
        out = "  " + pad(st[0], 10)
        for p in roll.policies():
            r = roll.rate(st, p, n)
            out = out + lpad(roll.percent_text(r[0], r[1]), 10)
        print(out)
    print("Below " + str(line) + "%:")
    lists = []
    for k in range(0, 4):
        f = roll.flagged(reg, roll.policies()[k], line)
        lists = lists + [f]
        if len(f) == 0:
            print("  (" + str(k + 1) + ") nobody")
        else:
            print("  (" + str(k + 1) + ") " + names_text(f))
    only = []
    for name in lists[0]:
        in3 = False
        for x in lists[2]:
            if x == name:
                in3 = True
        if not in3:
            only = only + [name]
    if len(only) > 0:
        print("Flagged only when a session with no mark counts as an absence: " + names_text(only) + ".")
    none = []
    for st in reg:
        if roll.rate(st, "marked", n)[1] == 0:
            none = none + [st[0]]
    if len(none) > 0:
        print("No marked session yet, so no rate: " + names_text(none) + ". Counting every session held gives 0.0% instead.")

def take_register(reg):
    names = []
    for st in reg:
        names = names + [st[0]]
    print("Session " + str(roll.held(reg) + 1) + ": a mark for each of " + names_text(names) + " in turn (P, L, E or A), or skip if the register was not taken.")
    t = plain(input("marks> "))
    if t == "":
        print("Cancelled.")
        return reg
    marks = []
    if t == "skip":
        for st in reg:
            marks = marks + ["-"]
    else:
        w = ""
        for c in t + " ":
            if c == " ":
                if w != "":
                    if len(w) != 1 or mark_of(w) == "":
                        print("Each mark is P, L, E or A, separated by spaces.")
                        return reg
                    marks = marks + [mark_of(w)]
                w = ""
            else:
                w = w + c
        if len(marks) != len(reg):
            print("Type " + str(len(reg)) + " marks, one for each student.")
            return reg
    out = roll.take(reg, marks)
    print("Session " + str(roll.held(out)) + " recorded.")
    return out

def correct_mark(reg):
    name = plain(input("student> "))
    if name == "":
        print("Cancelled.")
        return reg
    i = find(reg, name)
    if i < 0:
        print("No such student.")
        return reg
    t = plain(input("session (1 to " + str(roll.held(reg)) + ")> "))
    if t == "":
        print("Cancelled.")
        return reg
    k = 0
    if len(t) > 0 and len(t) <= 2:
        digits = True
        for c in t:
            if c < "0" or c > "9":
                digits = False
        if digits:
            k = int(t)
    if k < 1 or k > roll.held(reg):
        print("Type a session from 1 to " + str(roll.held(reg)) + ".")
        return reg
    if k < reg[i][1]:
        print(name + " joined at session " + str(reg[i][1]) + ".")
        return reg
    m = plain(input("mark (P, L, E, A or none)> "))
    if m == "":
        print("Cancelled.")
        return reg
    mark = "-"
    if m != "none":
        if len(m) != 1 or mark_of(m) == "":
            print("Type P, L, E, A or none.")
            return reg
        mark = mark_of(m)
    before = reg[i][2][k - 1]
    out = roll.correct(reg, i, k, mark)
    print(name + ", session " + str(k) + ": " + before + " -> " + mark + ".")
    return out

def add(reg):
    name = plain(input("name (up to 10 letters)> "))
    if name == "":
        print("Cancelled.")
        return reg
    if len(name) > 10:
        print("Type a name of up to 10 letters.")
        return reg
    for c in name:
        if not (c >= "a" and c <= "z" or c >= "A" and c <= "Z"):
            print("Type a name of up to 10 letters.")
            return reg
    if find(reg, name) >= 0:
        print(name + " is already on the register.")
        return reg
    if len(reg) >= 8:
        print("8 students is the most.")
        return reg
    out = roll.add_student(reg, name)
    print(name + " joins from session " + str(roll.held(out) + 1) + ".")
    return out

reg = roll.default_register()
line = 80
print("== Attendance register ==")
print("A mark for each student at each session: present, late, excused or absent - or no mark, when the register was not taken or the student had not joined.")
running = True
while running:
    print("")
    print("1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit")
    choice = plain(input("choice> "))
    if choice == "1":
        show(reg)
    elif choice == "2":
        rates(reg, line)
    elif choice == "3":
        reg = take_register(reg)
    elif choice == "4":
        reg = correct_mark(reg)
    elif choice == "5":
        reg = add(reg)
    elif choice == "6":
        running = False
    else:
        print("Pick a number from 1 to 6.")
print("Bye.")

roll.eml

eml
# P121 attendance register - students, sessions and a mark for each: P
# present, L late, E excused, A absent, or no mark at all (the register was
# not taken, or the student had not joined). As in the corpus case
# absent-vs-empty-vs-zero, an absence, a missing mark and a student with no
# sessions yet are three different nothings, and a rate that treats them
# alike flags the wrong students. As in attendance-register, the counts are
# a dict updated one mark at a time.
#
# A student is [name, session joined, marks]; marks holds one letter per
# session held, "-" for no mark.

def default_register():
    return [["Ana", 1, ["P", "P", "-", "L", "P", "A"]], ["Ben", 1, ["P", "A", "-", "P", "E", "P"]], ["Cara", 1, ["P", "P", "-", "P", "P", "L"]], ["Dana", 4, ["-", "-", "-", "P", "P", "P"]], ["Eli", 1, ["A", "L", "-", "E", "E", "A"]]]

def held(reg):
    if len(reg) == 0:
        return 0
    return len(reg[0][2])

def div(p, q):
    # the whole part of p / q, for p >= 0 and q > 0
    int(p / q) => f
    while f * q > p:
        f - 1 => f
    while (f + 1) * q <= p:
        f + 1 => f
    return f

def counts(st):
    # {letter: count} over a student's marks, "-" included
    {"P": 0, "L": 0, "E": 0, "A": 0, "-": 0} => c
    for m in st[2]:
        c[m] + 1 => c[m]
    return c

def policies():
    return ["all held", "since joining", "marked", "marked, not excused"]

def rate(st, policy, n):
    # [present or late, the sessions it is out of] under a policy
    counts(st) => c
    c["P"] + c["L"] => present
    if policy == "all held":
        return [present, n]
    if policy == "since joining":
        return [present, n - st[1] + 1]
    if policy == "marked":
        return [present, c["P"] + c["L"] + c["E"] + c["A"]]
    return [present, c["P"] + c["L"] + c["A"]]

def tenths(p, q):
    # p / q as a percentage in tenths, rounded half up
    return div(2000 * p + q, 2 * q)

def percent_text(p, q):
    if q <= 0:
        return "-"
    tenths(p, q) => t
    return str(div(t, 10)) + "." + str(t - 10 * div(t, 10)) + "%"

def below(p, q, line):
    # under the line (a whole percent): only when there are sessions to count
    return q > 0 and 100 * p < line * q

def flagged(reg, policy, line):
    held(reg) => n
    [] => out
    for st in reg:
        rate(st, policy, n) => r
        if below(r[0], r[1], line):
            out + [st[0]] => out
    return out

def not_taken(reg):
    # the sessions where nobody has a mark
    [] => out
    for k in [0:held(reg) - 1]:
        True => none
        for st in reg:
            if st[2][k] != "-":
                False => none
        if none:
            out + [k + 1] => out
    return out

def take(reg, marks):
    # a new session: one mark per student, or "-" for each when not taken
    [] => out
    for i in [0:len(reg) - 1]:
        reg[i] => st
        out + [[st[0], st[1], st[2] + [marks[i]]]] => out
    return out

def add_student(reg, name):
    held(reg) + 1 => j
    [] => marks
    for k in [1:held(reg)]:
        marks + ["-"] => marks
    return reg + [[name, j, marks]]

def correct(reg, i, k, mark):
    # the student i's mark for session k (1-based)
    [] => out
    for x in [0:len(reg) - 1]:
        reg[x] => st
        if x == i:
            [] => marks
            for y in [0:len(st[2]) - 1]:
                if y == k - 1:
                    marks + [mark] => marks
                else:
                    marks + [st[2][y]] => marks
            out + [[st[0], st[1], marks]] => out
        else:
            out + [st] => out
    return out
Python projection (roll.py)
def default_register():
    return [["Ana", 1, ["P", "P", "-", "L", "P", "A"]], ["Ben", 1, ["P", "A", "-", "P", "E", "P"]], ["Cara", 1, ["P", "P", "-", "P", "P", "L"]], ["Dana", 4, ["-", "-", "-", "P", "P", "P"]], ["Eli", 1, ["A", "L", "-", "E", "E", "A"]]]

def held(reg):
    if len(reg) == 0:
        return 0
    return len(reg[0][2])

def div(p, q):
    f = int(p / q)
    while f * q > p:
        f = f - 1
    while (f + 1) * q <= p:
        f = f + 1
    return f

def counts(st):
    c = {"P": 0, "L": 0, "E": 0, "A": 0, "-": 0}
    for m in st[2]:
        c[m] = c[m] + 1
    return c

def policies():
    return ["all held", "since joining", "marked", "marked, not excused"]

def rate(st, policy, n):
    c = counts(st)
    present = c["P"] + c["L"]
    if policy == "all held":
        return [present, n]
    if policy == "since joining":
        return [present, n - st[1] + 1]
    if policy == "marked":
        return [present, c["P"] + c["L"] + c["E"] + c["A"]]
    return [present, c["P"] + c["L"] + c["A"]]

def tenths(p, q):
    return div(2000 * p + q, 2 * q)

def percent_text(p, q):
    if q <= 0:
        return "-"
    t = tenths(p, q)
    return str(div(t, 10)) + "." + str(t - 10 * div(t, 10)) + "%"

def below(p, q, line):
    return q > 0 and 100 * p < line * q

def flagged(reg, policy, line):
    n = held(reg)
    out = []
    for st in reg:
        r = rate(st, policy, n)
        if below(r[0], r[1], line):
            out = out + [st[0]]
    return out

def not_taken(reg):
    out = []
    for k in range(0, held(reg)):
        none = True
        for st in reg:
            if st[2][k] != "-":
                none = False
        if none:
            out = out + [k + 1]
    return out

def take(reg, marks):
    out = []
    for i in range(0, len(reg)):
        st = reg[i]
        out = out + [[st[0], st[1], st[2] + [marks[i]]]]
    return out

def add_student(reg, name):
    j = held(reg) + 1
    marks = []
    for k in range(1, held(reg)+1):
        marks = marks + ["-"]
    return reg + [[name, j, marks]]

def correct(reg, i, k, mark):
    out = []
    for x in range(0, len(reg)):
        st = reg[x]
        if x == i:
            marks = []
            for y in range(0, len(st[2])):
                if y == k - 1:
                    marks = marks + [mark]
                else:
                    marks = marks + [st[2][y]]
            out = out + [[st[0], st[1], marks]]
        else:
            out = out + [st]
    return out

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.