Project P010

Quiz

Take a multiple-choice quiz, get a score, review the questions you missed and retake only those until none are left; every attempt is kept in a score history - from a text menu.

3 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

A multiple-choice quiz of eight questions. Taking the quiz gives a score and a percentage; the questions you got wrong can then be reviewed - your answer next to the right one - and retaken on their own, again and again, until none are left. Every scored attempt goes into a score history, which is kept while the program runs.

  • main.eml - the menu, the score report, the review of mistakes, and the score history
  • bank.eml - the questions: each has a number, the question, four options A to D, and the right letter
  • quiz.eml - asking a question, reading the answer, marking an attempt, and the percentage

An attempt asks its questions in order and keeps the ones answered wrongly, with the answer given. A retake asks only the last attempt's mistakes, and its own mistakes replace them, so a question answered right drops out. Typing q at an answer stops the attempt: a stopped attempt is not scored, goes into no history, and leaves the mistakes as they were. The percentage is worked out in whole numbers and rounded half up (7/8 shows as 88%).

What is checked: an answer is one letter, A to D in either case, or q; anything else asks the same question again.

Sessions: sessions/basic.in takes the quiz with two mistakes, reviews them, retakes them (one right, one wrong again), reviews and retakes the one that is left until there are none, and shows the three scores; sessions/bad-input.in types an unknown choice, asks for a review, a retake and the scores before any quiz, types a letter that is not an option, nothing, two letters and a lower-case answer, stops a quiz (it is not scored), then answers in lower case with one mistake, stops the retake, and retakes until nothing is left to review or retake.

Built on the verified corpus case simple-quiz-grader (answers marked against the right ones, with a score and a percentage).

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

== Quiz ==
1) take the quiz  2) review mistakes  3) retake mistakes  4) scores  5) quit
choice> 9
Pick a number from 1 to 5.

== Quiz ==
1) take the quiz  2) review mistakes  3) retake mistakes  4) scores  5) quit
choice> 2
Take the quiz first.

== Quiz ==
1) take the quiz  2) review mistakes  3) retake mistakes  4) scores  5) quit
choice> 3
Take the quiz first.

== Quiz ==
1) take the quiz  2) review mistakes  3) retake mistakes  4) scores  5) quit
choice> 4

-- scores --
  (no attempts yet)

== Quiz ==
1) take the quiz  2) review mistakes  3) retake mistakes  4) scores  5) quit
choice> 1

Q1. What gas do plants take in for photosynthesis?
   A) Oxygen
   B) Carbon dioxide
   C) Nitrogen
   D) Helium
answer> e
Answer with A, B, C or D (or q to stop).
answer> 
Answer with A, B, C or D (or q to stop).
answer> ab
Answer with A, B, C or D (or q to stop).
answer> b
Right.

Q2. How many bones are in the adult human body?
   A) 106
   B) 206
   C) 306
   D) 406
answer> q
Quiz stopped; not scored.

== Quiz ==
1) take the quiz  2) review mistakes  3) retake mistakes  4) scores  5) quit
choice> 4

-- scores --
  (no attempts yet)

== Quiz ==
1) take the quiz  2) review mistakes  3) retake mistakes  4) scores  5) quit
choice> 1

Q1. What gas do plants take in for photosynthesis?
   A) Oxygen
   B) Carbon dioxide
   C) Nitrogen
   D) Helium
answer> b
Right.

Q2. How many bones are in the adult human body?
   A) 106
   B) 206
   C) 306
   D) 406
answer> b
Right.

Q3. What is the chemical symbol for gold?
   A) Ag
   B) Gd
   C) Au
   D) Go
answer> c
Right.

Q4. Which planet is closest to the Sun?
   A) Venus
   B) Mars
   C) Mercury
   D) Earth
answer> c
Right.

Q5. What is the largest ocean on Earth?
   A) Atlantic
   B) Indian
   C) Arctic
   D) Pacific
answer> d
Right.

Q6. Which river flows through Cairo?
   A) Nile
   B) Amazon
   C) Danube
   D) Ganges
answer> a
Right.

Q7. On which continent is the Sahara desert?
   A) Asia
   B) Africa
   C) Australia
   D) South America
answer> b
Right.

Q8. What is the capital of Canada?
   A) Toronto
   B) Vancouver
   C) Ottawa
   D) Montreal
answer> a
Wrong - it is C) Ottawa.
Score: 7/8 (88%).
1 to review.

== Quiz ==
1) take the quiz  2) review mistakes  3) retake mistakes  4) scores  5) quit
choice> 3

Q8. What is the capital of Canada?
   A) Toronto
   B) Vancouver
   C) Ottawa
   D) Montreal
answer> x
Answer with A, B, C or D (or q to stop).
answer> q
Retake stopped; not scored.

== Quiz ==
1) take the quiz  2) review mistakes  3) retake mistakes  4) scores  5) quit
choice> 4

-- scores --
  1) quiz     7/8   88%

== Quiz ==
1) take the quiz  2) review mistakes  3) retake mistakes  4) scores  5) quit
choice> 3

Q8. What is the capital of Canada?
   A) Toronto
   B) Vancouver
   C) Ottawa
   D) Montreal
answer> c
Right.
Score: 1/1 (100%).
No mistakes - well done.

== Quiz ==
1) take the quiz  2) review mistakes  3) retake mistakes  4) scores  5) quit
choice> 3
No mistakes to retake.

== Quiz ==
1) take the quiz  2) review mistakes  3) retake mistakes  4) scores  5) quit
choice> 2
No mistakes to review.

== Quiz ==
1) take the quiz  2) review mistakes  3) retake mistakes  4) scores  5) quit
choice> 4

-- scores --
  1) quiz     7/8   88%
  2) retake   1/1  100%

== Quiz ==
1) take the quiz  2) review mistakes  3) retake mistakes  4) scores  5) quit
choice> 5
Bye.
What was typed (30 lines)
9
2
3
4
1
e

ab
b
q
4
1
b
b
c
c
d
a
b
a
3
x
q
4
3
c
3
2
4
5

basic

interpreter: byte-equal

== Quiz ==
1) take the quiz  2) review mistakes  3) retake mistakes  4) scores  5) quit
choice> 1

Q1. What gas do plants take in for photosynthesis?
   A) Oxygen
   B) Carbon dioxide
   C) Nitrogen
   D) Helium
answer> B
Right.

Q2. How many bones are in the adult human body?
   A) 106
   B) 206
   C) 306
   D) 406
answer> B
Right.

Q3. What is the chemical symbol for gold?
   A) Ag
   B) Gd
   C) Au
   D) Go
answer> A
Wrong - it is C) Au.

Q4. Which planet is closest to the Sun?
   A) Venus
   B) Mars
   C) Mercury
   D) Earth
answer> C
Right.

Q5. What is the largest ocean on Earth?
   A) Atlantic
   B) Indian
   C) Arctic
   D) Pacific
answer> D
Right.

Q6. Which river flows through Cairo?
   A) Nile
   B) Amazon
   C) Danube
   D) Ganges
answer> A
Right.

Q7. On which continent is the Sahara desert?
   A) Asia
   B) Africa
   C) Australia
   D) South America
answer> A
Wrong - it is B) Africa.

Q8. What is the capital of Canada?
   A) Toronto
   B) Vancouver
   C) Ottawa
   D) Montreal
answer> C
Right.
Score: 6/8 (75%).
2 to review.

== Quiz ==
1) take the quiz  2) review mistakes  3) retake mistakes  4) scores  5) quit
choice> 2

Q3. What is the chemical symbol for gold?
   you said A) Ag; it is C) Au.

Q7. On which continent is the Sahara desert?
   you said A) Asia; it is B) Africa.

== Quiz ==
1) take the quiz  2) review mistakes  3) retake mistakes  4) scores  5) quit
choice> 3

Q3. What is the chemical symbol for gold?
   A) Ag
   B) Gd
   C) Au
   D) Go
answer> C
Right.

Q7. On which continent is the Sahara desert?
   A) Asia
   B) Africa
   C) Australia
   D) South America
answer> C
Wrong - it is B) Africa.
Score: 1/2 (50%).
1 to review.

== Quiz ==
1) take the quiz  2) review mistakes  3) retake mistakes  4) scores  5) quit
choice> 2

Q7. On which continent is the Sahara desert?
   you said C) Australia; it is B) Africa.

== Quiz ==
1) take the quiz  2) review mistakes  3) retake mistakes  4) scores  5) quit
choice> 3

Q7. On which continent is the Sahara desert?
   A) Asia
   B) Africa
   C) Australia
   D) South America
answer> B
Right.
Score: 1/1 (100%).
No mistakes - well done.

== Quiz ==
1) take the quiz  2) review mistakes  3) retake mistakes  4) scores  5) quit
choice> 2
No mistakes to review.

== Quiz ==
1) take the quiz  2) review mistakes  3) retake mistakes  4) scores  5) quit
choice> 4

-- scores --
  1) quiz     6/8   75%
  2) retake   1/2   50%
  3) retake   1/1  100%

== Quiz ==
1) take the quiz  2) review mistakes  3) retake mistakes  4) scores  5) quit
choice> 5
Bye.
What was typed (19 lines)
1
B
B
A
C
D
A
A
C
2
3
C
C
2
3
B
2
4
5

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
# P010 quiz: take the quiz, review the questions you missed, and retake only
# those until none are left. Every scored attempt goes into the score
# history, which lives while the program runs.
import bank
import quiz

def report(score, total, mistakes):
    ("Score: " + str(score) + "/" + str(total) + " (" + str(quiz.percent(score, total)) + "%).") ^0
    if len(mistakes) == 0:
        "No mistakes - well done." ^0
    else:
        (str(len(mistakes)) + " to review.") ^0

def review(mistakes):
    for m in mistakes:
        m[0] => q
        "" ^0
        ("Q" + str(q[0]) + ". " + q[1]) ^0
        ("   you said " + m[1] + ") " + bank.option_text(q, m[1]) + "; it is " + q[3] + ") " + bank.option_text(q, q[3]) + ".") ^0

# [kind, score, total] for every scored attempt
[] => history
# None until the quiz has been taken; then the last attempt's mistakes
None => mistakes
True => running
while running:
    "" ^0
    "== Quiz ==" ^0
    "1) take the quiz  2) review mistakes  3) retake mistakes  4) scores  5) quit" ^0
    quiz.trim(input("choice> ")) => choice
    if choice == "1":
        quiz.attempt(bank.questions) => r
        if r == None:
            "Quiz stopped; not scored." ^0
        else:
            report(r[0], len(bank.questions), r[1])
            history + [["quiz", r[0], len(bank.questions)]] => history
            r[1] => mistakes
    elif choice == "2":
        if mistakes == None:
            "Take the quiz first." ^0
        elif len(mistakes) == 0:
            "No mistakes to review." ^0
        else:
            review(mistakes)
    elif choice == "3":
        if mistakes == None:
            "Take the quiz first." ^0
        elif len(mistakes) == 0:
            "No mistakes to retake." ^0
        else:
            [m[0] for m in mistakes] => again
            quiz.attempt(again) => r
            if r == None:
                "Retake stopped; not scored." ^0
            else:
                report(r[0], len(again), r[1])
                history + [["retake", r[0], len(again)]] => history
                r[1] => mistakes
    elif choice == "4":
        "" ^0
        "-- scores --" ^0
        if len(history) == 0:
            "  (no attempts yet)" ^0
        0 => n
        for h in history:
            n + 1 => n
            ("  " + str(n) + ") " + ("%-7s" % h[0]) + ("%5s" % (str(h[1]) + "/" + str(h[2]))) + ("%6s" % (str(quiz.percent(h[1], h[2])) + "%"))) ^0
    elif choice == "5":
        False => running
    else:
        "Pick a number from 1 to 5." ^0
"Bye." ^0
Python projection (main.py)
import bank
import quiz

def report(score, total, mistakes):
    print("Score: " + str(score) + "/" + str(total) + " (" + str(quiz.percent(score, total)) + "%).")
    if len(mistakes) == 0:
        print("No mistakes - well done.")
    else:
        print(str(len(mistakes)) + " to review.")

def review(mistakes):
    for m in mistakes:
        q = m[0]
        print("")
        print("Q" + str(q[0]) + ". " + q[1])
        print("   you said " + m[1] + ") " + bank.option_text(q, m[1]) + "; it is " + q[3] + ") " + bank.option_text(q, q[3]) + ".")

history = []
mistakes = None
running = True
while running:
    print("")
    print("== Quiz ==")
    print("1) take the quiz  2) review mistakes  3) retake mistakes  4) scores  5) quit")
    choice = quiz.trim(input("choice> "))
    if choice == "1":
        r = quiz.attempt(bank.questions)
        if r == None:
            print("Quiz stopped; not scored.")
        else:
            report(r[0], len(bank.questions), r[1])
            history = history + [["quiz", r[0], len(bank.questions)]]
            mistakes = r[1]
    elif choice == "2":
        if mistakes == None:
            print("Take the quiz first.")
        elif len(mistakes) == 0:
            print("No mistakes to review.")
        else:
            review(mistakes)
    elif choice == "3":
        if mistakes == None:
            print("Take the quiz first.")
        elif len(mistakes) == 0:
            print("No mistakes to retake.")
        else:
            again = [m[0] for m in mistakes]
            r = quiz.attempt(again)
            if r == None:
                print("Retake stopped; not scored.")
            else:
                report(r[0], len(again), r[1])
                history = history + [["retake", r[0], len(again)]]
                mistakes = r[1]
    elif choice == "4":
        print("")
        print("-- scores --")
        if len(history) == 0:
            print("  (no attempts yet)")
        n = 0
        for h in history:
            n = n + 1
            print("  " + str(n) + ") " + "%-7s" % h[0] + "%5s" % (str(h[1]) + "/" + str(h[2])) + "%6s" % (str(quiz.percent(h[1], h[2])) + "%"))
    elif choice == "5":
        running = False
    else:
        print("Pick a number from 1 to 5.")
print("Bye.")

bank.eml

eml
# P010 quiz - the question bank. A question is a list
# [number, text, [option A, B, C, D], correct letter].

[
    [1, "What gas do plants take in for photosynthesis?", ["Oxygen", "Carbon dioxide", "Nitrogen", "Helium"], "B"],
    [2, "How many bones are in the adult human body?", ["106", "206", "306", "406"], "B"],
    [3, "What is the chemical symbol for gold?", ["Ag", "Gd", "Au", "Go"], "C"],
    [4, "Which planet is closest to the Sun?", ["Venus", "Mars", "Mercury", "Earth"], "C"],
    [5, "What is the largest ocean on Earth?", ["Atlantic", "Indian", "Arctic", "Pacific"], "D"],
    [6, "Which river flows through Cairo?", ["Nile", "Amazon", "Danube", "Ganges"], "A"],
    [7, "On which continent is the Sahara desert?", ["Asia", "Africa", "Australia", "South America"], "B"],
    [8, "What is the capital of Canada?", ["Toronto", "Vancouver", "Ottawa", "Montreal"], "C"],
] => questions

"ABCD" => letters

def option_text(q, letter):
    # The text of option `letter` of question q.
    for i in [0:3]:
        if letters[i] == letter:
            return q[2][i]
    return ""
Python projection (bank.py)
questions = [[1, "What gas do plants take in for photosynthesis?", ["Oxygen", "Carbon dioxide", "Nitrogen", "Helium"], "B"], [2, "How many bones are in the adult human body?", ["106", "206", "306", "406"], "B"], [3, "What is the chemical symbol for gold?", ["Ag", "Gd", "Au", "Go"], "C"], [4, "Which planet is closest to the Sun?", ["Venus", "Mars", "Mercury", "Earth"], "C"], [5, "What is the largest ocean on Earth?", ["Atlantic", "Indian", "Arctic", "Pacific"], "D"], [6, "Which river flows through Cairo?", ["Nile", "Amazon", "Danube", "Ganges"], "A"], [7, "On which continent is the Sahara desert?", ["Asia", "Africa", "Australia", "South America"], "B"], [8, "What is the capital of Canada?", ["Toronto", "Vancouver", "Ottawa", "Montreal"], "C"]]
letters = "ABCD"

def option_text(q, letter):
    for i in range(0, 4):
        if letters[i] == letter:
            return q[2][i]
    return ""

quiz.eml

eml
# P010 quiz - asking questions and marking them. An attempt asks a list of
# questions in order and returns [score, mistakes], where each mistake is
# [question, the letter given]; typing q stops the attempt, and a stopped
# attempt is not scored.
import bank

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 answer_letter(typed):
    # "A".."D" for a typed a-d or A-D, "q" to stop, "" for anything else.
    if typed == "q" or typed == "Q":
        return "q"
    "abcd" => small
    if len(typed) == 1:
        for i in [0:3]:
            if typed == bank.letters[i] or typed == small[i]:
                return bank.letters[i]
    return ""

def ask(q):
    # Shows question q and returns the letter answered, or "q".
    "" ^0
    ("Q" + str(q[0]) + ". " + q[1]) ^0
    for i in [0:3]:
        ("   " + bank.letters[i] + ") " + q[2][i]) ^0
    while True:
        answer_letter(trim(input("answer> "))) => a
        if a != "":
            return a
        "Answer with A, B, C or D (or q to stop)." ^0

def attempt(qs):
    # [score, mistakes] for the questions qs, or None if the attempt stopped.
    0 => score
    [] => mistakes
    for q in qs:
        ask(q) => a
        if a == "q":
            return None
        if a == q[3]:
            score + 1 => score
            "Right." ^0
        else:
            ("Wrong - it is " + q[3] + ") " + bank.option_text(q, q[3]) + ".") ^0
            mistakes + [[q, a]] => mistakes
    return [score, mistakes]

def percent(score, total):
    # score / total as a whole percentage, rounded half up.
    return int((200 * score + total - (200 * score + total) % (2 * total)) / (2 * total))
Python projection (quiz.py)
import bank

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 answer_letter(typed):
    if typed == "q" or typed == "Q":
        return "q"
    small = "abcd"
    if len(typed) == 1:
        for i in range(0, 4):
            if typed == bank.letters[i] or typed == small[i]:
                return bank.letters[i]
    return ""

def ask(q):
    print("")
    print("Q" + str(q[0]) + ". " + q[1])
    for i in range(0, 4):
        print("   " + bank.letters[i] + ") " + q[2][i])
    while True:
        a = answer_letter(trim(input("answer> ")))
        if a != "":
            return a
        print("Answer with A, B, C or D (or q to stop).")

def attempt(qs):
    score = 0
    mistakes = []
    for q in qs:
        a = ask(q)
        if a == "q":
            return None
        if a == q[3]:
            score = score + 1
            print("Right.")
        else:
            print("Wrong - it is " + q[3] + ") " + bank.option_text(q, q[3]) + ".")
            mistakes = mistakes + [[q, a]]
    return [score, mistakes]

def percent(score, total):
    return int((200 * score + total - (200 * score + total) % (2 * total)) / (2 * total))

Built on these corpus cases