{
  "id": "P056",
  "slug": "mini-spreadsheet",
  "key": "P056-mini-spreadsheet",
  "title": "Mini spreadsheet",
  "summary": "Cells A1 to F9 holding numbers, labels and formulas - + - * / with parentheses, cell references, SUM, MIN, MAX and AVG over ranges - kept as exact fractions. Every change recalculates the sheet in dependency order (Kahn's algorithm), and formulas caught in a cycle are found and marked instead of looping.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P056 - Mini spreadsheet\n\nA small spreadsheet: columns A to F, rows 1 to 9. A cell holds a number, a\nlabel, or a formula that starts with =. Formulas use + - * / and\nparentheses, cells like B3, and SUM, MIN, MAX and AVG over cells and\nranges like D2:D4. Every change works the whole sheet out again, in an\norder where each formula comes after the formulas it reads. A formula\ncaught in a cycle is found and marked instead of looping forever.\n\n- `main.eml` - the menu, reading cells and contents, the value and formula\n  views, and the sample\n- `sheet.eml` - formulas: the tokens, a parser that also works the value\n  out, and the recalculation order\n- `frac.eml` - exact fractions, from P007 by way of P038 and P044\n\nHow each part works:\n\n- Values are exact fractions, so 1.2 * 10 is exactly 12 and a third is a\n  third. The value view shows two decimal places and marks a rounded value\n  with ~.\n- A formula is read by recursive descent: an expression is terms joined by\n  + and -, a term is factors joined by * and /, and a factor is a number,\n  a cell, a function, a minus sign before a factor, or an expression in\n  parentheses. That is why =2+3*4 is 14. A formula that does not parse is\n  refused with the reason, and nothing changes.\n- An empty cell counts as 0 in arithmetic. Arithmetic on a label gives\n  #TEXT, and dividing by zero gives #DIV/0; an error passes on to every\n  formula that reads it. SUM, MIN, MAX and AVG skip empty cells and\n  labels. AVG of nothing is #DIV/0, and MIN or MAX of nothing is 0.\n- The order is Kahn's algorithm, as in the corpus case `topological-sort`.\n  A formula is ready when every formula it reads is done; doing it may\n  make others ready. Formulas never made ready are in a cycle, or read\n  something that is, and they show #CYCLE until the cycle is broken. The\n  order view lists both.\n\nWhat is checked: menu choices 1 to 7; cells from A1 to F9 (either case);\ncontents up to 60 characters; formulas that parse - numbers, cells A1 to\nF9, + - * / ( ), and SUM, MIN, MAX, AVG with cells and ranges separated by\ncommas. An empty answer cancels.\n\nSessions: `sessions/basic.in` loads a small order: three items, their\ntotals, a subtotal, tax at 8%, the total and the average.\n- The views show values, formulas and the order they were worked out:\n  D2, D3, D4, D6, D9, D7, D8.\n- B3 changes to 1.25, and seven formulas are worked out again.\n- B7 becomes =D8/100. D7 reads B7 and D8 reads D7, so the three make a\n  cycle; the rest still work.\n- B7 becomes 0.1, which breaks the cycle: tax 3.7 and total 40.7.\n\n`sessions/bad-input.in` gives:\n- menu choices 0 and x;\n- cells G1 and A0, and an empty cell;\n- empty contents;\n- the broken formulas =1+ and =SUM(B1;\n- =A1+1 in A1, a cycle of one;\n- =B1/C1 on empty cells;\n- a label, and a formula that multiplies it;\n- a SUM over the label, that formula and an empty cell;\n- 61 characters;\n- AVG of an empty column;\n- A1 emptied, then the values, the formulas and the order.\n\nBuilt on the verified corpus case `topological-sort` (Kahn's algorithm,\nwhich finds the cycle in what it leaves over).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P056 mini spreadsheet: six columns and nine rows of numbers, labels and\n# formulas. Formulas use + - * / ( ), cells like B3 and SUM, MIN, MAX, AVG\n# over ranges like A1:A5; every change recalculates the sheet in an order\n# where each formula comes after the formulas it reads, and a cycle is\n# found and named instead of looping forever.\nimport frac\nimport sheet\n\ndef trim(s):\n    0 => i\n    len(s) => j\n    while i < j and s[i] == \" \":\n        i + 1 => i\n    while j > i and s[j - 1] == \" \":\n        j - 1 => j\n    return s[i:j]\n\ndef shown(v):\n    # A value in at most 10 characters: numbers to two decimal places with\n    # ~ in front when rounded, labels cut to fit, errors as they are.\n    if v[0] == \"e\":\n        return v[1]\n    if v[0] == \"t\":\n        if len(v[1]) > 10:\n            return v[1][0:9] + \"~\"\n        return v[1]\n    frac.decimal(v[1], 2) => s\n    if len(s) > 6 and s[0:6] == \"about \":\n        \"~\" + s[6:len(s)] => s\n    if len(s) > 10:\n        return \"#WIDE\"\n    return s\n\ndef grid(cells, vals, formulas):\n    \"     \" => head\n    for c in [0:5]:\n        \"          \" + \"ABCDEF\"[c] => h\n        head + h[len(h) - 10:len(h)] + \" \" => head\n    trim_right(head) ^0\n    for r in [0:8]:\n        \" \" + str(r + 1) + \"   \" => line\n        for c in [0:5]:\n            r * 6 + c => k\n            if formulas:\n                cells[k] => s\n                if len(s) > 10:\n                    s[0:9] + \"~\" => s\n            else:\n                shown(vals[k]) => s\n            if formulas or vals[k][0] == \"t\":\n                while len(s) < 10:\n                    s + \" \" => s\n            else:\n                while len(s) < 10:\n                    \" \" + s => s\n            line + s + \" \" => line\n        trim_right(line) ^0\n\ndef trim_right(s):\n    len(s) => j\n    while j > 0 and s[j - 1] == \" \":\n        j - 1 => j\n    return s[0:j]\n\ndef ask_cell(prompt):\n    while True:\n        trim(input(prompt)) => answer\n        if answer == \"\":\n            return -1\n        sheet.cell_index(answer) => k\n        if k >= 0:\n            return k\n        \"Type a cell from A1 to F9, like B3.\" ^0\n\ndef set_cell(state):\n    ask_cell(\"cell> \") => k\n    if k == -1:\n        \"Cancelled.\" ^0\n        return\n    trim(input(\"content of \" + sheet.cell_name(k) + \" (a number, a label, or =formula)> \")) => text\n    if text == \"\":\n        \"Cancelled - use 2) to empty a cell.\" ^0\n        return\n    if len(text) > 60:\n        \"Keep a cell to 60 characters.\" ^0\n        return\n    if text[0] == \"=\":\n        sheet.checked(text[1:len(text)]) => c\n        if c[0] != \"\":\n            (\"Not a formula: \" + c[0] + \". Nothing was changed.\") ^0\n            return\n    text => state[0][k]\n    update(state, k)\n\ndef update(state, k):\n    sheet.recalculated(state[0]) => res\n    res[0] => state[1]\n    res[1] => state[2]\n    sheet.cell_name(k) + \" = \" + shown(state[1][k]) => what\n    if state[0][k] == \"\":\n        sheet.cell_name(k) + \" is empty now\" => what\n    (what + \"; \" + str(len(state[2])) + \" formula cell(s) worked out again.\") ^0\n    0 => stuck\n    for j in [0:53]:\n        if state[0][j] != \"\" and state[0][j][0] == \"=\" and state[1][j][0] == \"e\" and state[1][j][1] == \"#CYCLE\":\n            stuck + 1 => stuck\n    if stuck > 0:\n        (str(stuck) + \" formula cell(s) are in a cycle, or read one - they show #CYCLE until it is broken.\") ^0\n\ndef clear_cell(state):\n    ask_cell(\"cell to empty> \") => k\n    if k == -1:\n        \"Cancelled.\" ^0\n        return\n    \"\" => state[0][k]\n    update(state, k)\n\ndef order(state):\n    if len(state[2]) == 0:\n        \"No formula has been worked out yet.\" ^0\n    else:\n        \"\" => s\n        for k in state[2]:\n            if s != \"\":\n                s + \", \" => s\n            s + sheet.cell_name(k) => s\n        (\"Worked out in this order: \" + s + \".\") ^0\n    \"\" => stuck\n    for j in [0:53]:\n        if state[0][j] != \"\" and state[0][j][0] == \"=\" and state[1][j][1] == \"#CYCLE\":\n            if stuck != \"\":\n                stuck + \", \" => stuck\n            stuck + sheet.cell_name(j) => stuck\n    if stuck != \"\":\n        (\"In a cycle, or reading one: \" + stuck + \".\") ^0\n\ndef sample(state):\n    [\"\"] * 54 => cells\n    [[\"A1\", \"Item\"], [\"B1\", \"Price\"], [\"C1\", \"Qty\"], [\"D1\", \"Total\"],\n     [\"A2\", \"Paper\"], [\"B2\", \"4.5\"], [\"C2\", \"3\"], [\"D2\", \"=B2*C2\"],\n     [\"A3\", \"Pens\"], [\"B3\", \"1.2\"], [\"C3\", \"10\"], [\"D3\", \"=B3*C3\"],\n     [\"A4\", \"Folders\"], [\"B4\", \"2.75\"], [\"C4\", \"4\"], [\"D4\", \"=B4*C4\"],\n     [\"A6\", \"Subtotal\"], [\"D6\", \"=SUM(D2:D4)\"],\n     [\"A7\", \"Tax rate\"], [\"B7\", \"0.08\"], [\"D7\", \"=D6*B7\"],\n     [\"A8\", \"Total\"], [\"D8\", \"=D6+D7\"],\n     [\"A9\", \"Average\"], [\"D9\", \"=AVG(D2:D4)\"]] => entries\n    for e in entries:\n        e[1] => cells[sheet.cell_index(e[0])]\n    cells => state[0]\n    sheet.recalculated(cells) => res\n    res[0] => state[1]\n    res[1] => state[2]\n    \"A small order: three items, their totals, tax at 8% and an average.\" ^0\n\n\"== Mini spreadsheet ==\" ^0\n\"Cells A1 to F9 hold numbers, labels, or formulas such as =B2*C2 or =SUM(D2:D4).\" ^0\n# [cell texts, cell values, the order formulas were worked out]\n[[\"\"] * 54, [], []] => state\nsheet.recalculated(state[0]) => first\nfirst[0] => state[1]\nTrue => running\nwhile running:\n    \"\" ^0\n    \"1) set a cell  2) empty a cell  3) values  4) formulas  5) order  6) sample  7) quit\" ^0\n    trim(input(\"choice> \")) => choice\n    if choice == \"1\":\n        set_cell(state)\n    elif choice == \"2\":\n        clear_cell(state)\n    elif choice == \"3\":\n        grid(state[0], state[1], False)\n    elif choice == \"4\":\n        grid(state[0], state[1], True)\n    elif choice == \"5\":\n        order(state)\n    elif choice == \"6\":\n        sample(state)\n        grid(state[0], state[1], False)\n    elif choice == \"7\":\n        False => running\n    else:\n        \"Pick a number from 1 to 7.\" ^0\n\"Bye.\" ^0\n",
      "python": "import frac\nimport sheet\n\ndef trim(s):\n    i = 0\n    j = len(s)\n    while i < j and s[i] == \" \":\n        i = i + 1\n    while j > i and s[j - 1] == \" \":\n        j = j - 1\n    return s[i:j]\n\ndef shown(v):\n    if v[0] == \"e\":\n        return v[1]\n    if v[0] == \"t\":\n        if len(v[1]) > 10:\n            return v[1][0:9] + \"~\"\n        return v[1]\n    s = frac.decimal(v[1], 2)\n    if len(s) > 6 and s[0:6] == \"about \":\n        s = \"~\" + s[6:len(s)]\n    if len(s) > 10:\n        return \"#WIDE\"\n    return s\n\ndef grid(cells, vals, formulas):\n    head = \"     \"\n    for c in range(0, 6):\n        h = \"          \" + \"ABCDEF\"[c]\n        head = head + h[len(h) - 10:len(h)] + \" \"\n    print(trim_right(head))\n    for r in range(0, 9):\n        line = \" \" + str(r + 1) + \"   \"\n        for c in range(0, 6):\n            k = r * 6 + c\n            if formulas:\n                s = cells[k]\n                if len(s) > 10:\n                    s = s[0:9] + \"~\"\n            else:\n                s = shown(vals[k])\n            if formulas or vals[k][0] == \"t\":\n                while len(s) < 10:\n                    s = s + \" \"\n            else:\n                while len(s) < 10:\n                    s = \" \" + s\n            line = line + s + \" \"\n        print(trim_right(line))\n\ndef trim_right(s):\n    j = len(s)\n    while j > 0 and s[j - 1] == \" \":\n        j = j - 1\n    return s[0:j]\n\ndef ask_cell(prompt):\n    while True:\n        answer = trim(input(prompt))\n        if answer == \"\":\n            return -1\n        k = sheet.cell_index(answer)\n        if k >= 0:\n            return k\n        print(\"Type a cell from A1 to F9, like B3.\")\n\ndef set_cell(state):\n    k = ask_cell(\"cell> \")\n    if k == -1:\n        print(\"Cancelled.\")\n        return\n    text = trim(input(\"content of \" + sheet.cell_name(k) + \" (a number, a label, or =formula)> \"))\n    if text == \"\":\n        print(\"Cancelled - use 2) to empty a cell.\")\n        return\n    if len(text) > 60:\n        print(\"Keep a cell to 60 characters.\")\n        return\n    if text[0] == \"=\":\n        c = sheet.checked(text[1:len(text)])\n        if c[0] != \"\":\n            print(\"Not a formula: \" + c[0] + \". Nothing was changed.\")\n            return\n    state[0][k] = text\n    update(state, k)\n\ndef update(state, k):\n    res = sheet.recalculated(state[0])\n    state[1] = res[0]\n    state[2] = res[1]\n    what = sheet.cell_name(k) + \" = \" + shown(state[1][k])\n    if state[0][k] == \"\":\n        what = sheet.cell_name(k) + \" is empty now\"\n    print(what + \"; \" + str(len(state[2])) + \" formula cell(s) worked out again.\")\n    stuck = 0\n    for j in range(0, 54):\n        if state[0][j] != \"\" and state[0][j][0] == \"=\" and state[1][j][0] == \"e\" and state[1][j][1] == \"#CYCLE\":\n            stuck = stuck + 1\n    if stuck > 0:\n        print(str(stuck) + \" formula cell(s) are in a cycle, or read one - they show #CYCLE until it is broken.\")\n\ndef clear_cell(state):\n    k = ask_cell(\"cell to empty> \")\n    if k == -1:\n        print(\"Cancelled.\")\n        return\n    state[0][k] = \"\"\n    update(state, k)\n\ndef order(state):\n    if len(state[2]) == 0:\n        print(\"No formula has been worked out yet.\")\n    else:\n        s = \"\"\n        for k in state[2]:\n            if s != \"\":\n                s = s + \", \"\n            s = s + sheet.cell_name(k)\n        print(\"Worked out in this order: \" + s + \".\")\n    stuck = \"\"\n    for j in range(0, 54):\n        if state[0][j] != \"\" and state[0][j][0] == \"=\" and state[1][j][1] == \"#CYCLE\":\n            if stuck != \"\":\n                stuck = stuck + \", \"\n            stuck = stuck + sheet.cell_name(j)\n    if stuck != \"\":\n        print(\"In a cycle, or reading one: \" + stuck + \".\")\n\ndef sample(state):\n    cells = [\"\"] * 54\n    entries = [[\"A1\", \"Item\"], [\"B1\", \"Price\"], [\"C1\", \"Qty\"], [\"D1\", \"Total\"], [\"A2\", \"Paper\"], [\"B2\", \"4.5\"], [\"C2\", \"3\"], [\"D2\", \"=B2*C2\"], [\"A3\", \"Pens\"], [\"B3\", \"1.2\"], [\"C3\", \"10\"], [\"D3\", \"=B3*C3\"], [\"A4\", \"Folders\"], [\"B4\", \"2.75\"], [\"C4\", \"4\"], [\"D4\", \"=B4*C4\"], [\"A6\", \"Subtotal\"], [\"D6\", \"=SUM(D2:D4)\"], [\"A7\", \"Tax rate\"], [\"B7\", \"0.08\"], [\"D7\", \"=D6*B7\"], [\"A8\", \"Total\"], [\"D8\", \"=D6+D7\"], [\"A9\", \"Average\"], [\"D9\", \"=AVG(D2:D4)\"]]\n    for e in entries:\n        cells[sheet.cell_index(e[0])] = e[1]\n    state[0] = cells\n    res = sheet.recalculated(cells)\n    state[1] = res[0]\n    state[2] = res[1]\n    print(\"A small order: three items, their totals, tax at 8% and an average.\")\n\nprint(\"== Mini spreadsheet ==\")\nprint(\"Cells A1 to F9 hold numbers, labels, or formulas such as =B2*C2 or =SUM(D2:D4).\")\nstate = [[\"\"] * 54, [], []]\nfirst = sheet.recalculated(state[0])\nstate[1] = first[0]\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"1) set a cell  2) empty a cell  3) values  4) formulas  5) order  6) sample  7) quit\")\n    choice = trim(input(\"choice> \"))\n    if choice == \"1\":\n        set_cell(state)\n    elif choice == \"2\":\n        clear_cell(state)\n    elif choice == \"3\":\n        grid(state[0], state[1], False)\n    elif choice == \"4\":\n        grid(state[0], state[1], True)\n    elif choice == \"5\":\n        order(state)\n    elif choice == \"6\":\n        sample(state)\n        grid(state[0], state[1], False)\n    elif choice == \"7\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 7.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "sheet.eml",
      "eml": "# P056 mini spreadsheet - formulas and recalculation. The sheet has columns\n# A to F and rows 1 to 9; cell k (0 to 53) is column k % 6, row k / 6.\n# A cell holds text as typed: \"\" (empty), a number, a formula starting\n# with \"=\", or any other text (a label). A value is [\"n\", fraction],\n# [\"t\", text] or [\"e\", error].\nimport frac\n\ndef cell_name(k):\n    return \"ABCDEF\"[k % 6] + str(int(k / 6) + 1)\n\ndef cell_index(s):\n    # \"B3\" or \"b3\" as 0 to 53, or -1.\n    if len(s) != 2:\n        return -1\n    -1 => col\n    for c in [0:5]:\n        if s[0] == \"ABCDEF\"[c] or s[0] == \"abcdef\"[c]:\n            c => col\n    if col == -1 or not (s[1] >= \"1\" and s[1] <= \"9\"):\n        return -1\n    return (int(s[1]) - 1) * 6 + col\n\ndef tokens(f):\n    # The formula text after \"=\" as tokens: [\"num\", fraction], [\"cell\", k],\n    # [\"fn\", name], [\"op\", \"+\"] and so on; or [[\"bad\", why]].\n    [] => out\n    0 => i\n    while i < len(f):\n        f[i] => c\n        if c == \" \":\n            i + 1 => i\n        elif c == \"+\" or c == \"-\" or c == \"*\" or c == \"/\" or c == \"(\" or c == \")\" or c == \":\" or c == \",\":\n            out + [[\"op\", c]] => out\n            i + 1 => i\n        elif c >= \"0\" and c <= \"9\" or c == \".\":\n            i => j\n            while j < len(f) and (f[j] >= \"0\" and f[j] <= \"9\" or f[j] == \".\"):\n                j + 1 => j\n            frac.parse(f[i:j]) => x\n            if len(x) == 0:\n                return [[\"bad\", \"the number \" + f[i:j]]]\n            out + [[\"num\", x]] => out\n            j => i\n        elif (c >= \"A\" and c <= \"Z\") or (c >= \"a\" and c <= \"z\"):\n            i => j\n            while j < len(f) and ((f[j] >= \"A\" and f[j] <= \"Z\") or (f[j] >= \"a\" and f[j] <= \"z\") or (f[j] >= \"0\" and f[j] <= \"9\")):\n                j + 1 => j\n            f[i:j] => w\n            cell_index(w) => k\n            if k >= 0:\n                out + [[\"cell\", k]] => out\n            else:\n                \"\" => up\n                for ch in w:\n                    ch => u\n                    for q in [0:25]:\n                        if ch == \"abcdefghijklmnopqrstuvwxyz\"[q]:\n                            \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"[q] => u\n                    up + u => up\n                if up == \"SUM\" or up == \"MIN\" or up == \"MAX\" or up == \"AVG\":\n                    out + [[\"fn\", up]] => out\n                else:\n                    return [[\"bad\", w + \", which is not a cell from A1 to F9 or one of SUM, MIN, MAX, AVG\"]]\n            j => i\n        else:\n            return [[\"bad\", \"the character \" + c]]\n    return out\n\n# The parser walks the tokens with a position kept in p[0]. Each step\n# returns a value, given the values of all cells so far (vals); parsing\n# alone (vals = []) only checks the shape and collects the cells read.\n\ndef peek(ts, p):\n    if p[0] < len(ts):\n        return ts[p[0]]\n    return [\"end\", \"\"]\n\ndef is_op(t, c):\n    return t[0] == \"op\" and t[1] == c\n\ndef arith(a, b, op):\n    if a[0] == \"e\":\n        return a\n    if b[0] == \"e\":\n        return b\n    if a[0] == \"t\" or b[0] == \"t\":\n        return [\"e\", \"#TEXT\"]\n    if op == \"+\":\n        return [\"n\", frac.plus(a[1], b[1])]\n    if op == \"-\":\n        return [\"n\", frac.minus(a[1], b[1])]\n    if op == \"*\":\n        return [\"n\", frac.times(a[1], b[1])]\n    if frac.is_zero(b[1]):\n        return [\"e\", \"#DIV/0\"]\n    return [\"n\", frac.over(a[1], b[1])]\n\ndef read_cell(k, vals, refs):\n    # refs is [the list of cells read so far], kept in a one-item list so\n    # every call adds to the same one.\n    refs[0] + [k] => refs[0]\n    if len(vals) == 0:\n        return [\"n\", [0, 1]]\n    vals[k] => v\n    if v[0] == \"t\" and v[1] == \"\":\n        return [\"n\", [0, 1]]\n    return v\n\ndef expr(ts, p, vals, refs):\n    term(ts, p, vals, refs) => v\n    while is_op(peek(ts, p), \"+\") or is_op(peek(ts, p), \"-\"):\n        peek(ts, p)[1] => op\n        p[0] + 1 => p[0]\n        arith(v, term(ts, p, vals, refs), op) => v\n    return v\n\ndef term(ts, p, vals, refs):\n    factor(ts, p, vals, refs) => v\n    while is_op(peek(ts, p), \"*\") or is_op(peek(ts, p), \"/\"):\n        peek(ts, p)[1] => op\n        p[0] + 1 => p[0]\n        arith(v, factor(ts, p, vals, refs), op) => v\n    return v\n\ndef factor(ts, p, vals, refs):\n    peek(ts, p) => t\n    if is_op(t, \"-\"):\n        p[0] + 1 => p[0]\n        return arith([\"n\", [0, 1]], factor(ts, p, vals, refs), \"-\")\n    if t[0] == \"num\":\n        p[0] + 1 => p[0]\n        return [\"n\", t[1]]\n    if t[0] == \"cell\":\n        p[0] + 1 => p[0]\n        return read_cell(t[1], vals, refs)\n    if is_op(t, \"(\"):\n        p[0] + 1 => p[0]\n        expr(ts, p, vals, refs) => v\n        if not is_op(peek(ts, p), \")\"):\n            [\"bad\", \"a ( without its )\"] => p[1]\n        else:\n            p[0] + 1 => p[0]\n        return v\n    if t[0] == \"fn\":\n        p[0] + 1 => p[0]\n        return function(t[1], ts, p, vals, refs)\n    [\"bad\", \"something missing where a number, a cell or ( should be\"] => p[1]\n    return [\"e\", \"#BAD\"]\n\ndef function(fname, ts, p, vals, refs):\n    # SUM, MIN, MAX or AVG of cells and ranges: (A1:A5) or (A1, B2, C1:C3).\n    if not is_op(peek(ts, p), \"(\"):\n        [\"bad\", fname + \" without ( after it\"] => p[1]\n        return [\"e\", \"#BAD\"]\n    p[0] + 1 => p[0]\n    [] => cells\n    while True:\n        peek(ts, p) => a\n        if a[0] != \"cell\":\n            [\"bad\", fname + \"( with something other than cells and ranges in it, where \" + fname + \"(A1:A5) would do\"] => p[1]\n            return [\"e\", \"#BAD\"]\n        p[0] + 1 => p[0]\n        if is_op(peek(ts, p), \":\"):\n            p[0] + 1 => p[0]\n            peek(ts, p) => b\n            if b[0] != \"cell\":\n                [\"bad\", \"a range with no cell after its :\"] => p[1]\n                return [\"e\", \"#BAD\"]\n            p[0] + 1 => p[0]\n            a[1] % 6 => c1\n            b[1] % 6 => c2\n            int(a[1] / 6) => r1\n            int(b[1] / 6) => r2\n            if c2 < c1:\n                c1 => t\n                c2 => c1\n                t => c2\n            if r2 < r1:\n                r1 => t\n                r2 => r1\n                t => r2\n            for r in [r1:r2]:\n                for c in [c1:c2]:\n                    cells + [r * 6 + c] => cells\n        else:\n            cells + [a[1]] => cells\n        if is_op(peek(ts, p), \",\"):\n            p[0] + 1 => p[0]\n        elif is_op(peek(ts, p), \")\"):\n            p[0] + 1 => p[0]\n            break\n        else:\n            [\"bad\", fname + \"( with no , between two cells or no ) at the end\"] => p[1]\n            return [\"e\", \"#BAD\"]\n    # Empty cells and labels are left out, as spreadsheets do; an error in\n    # any cell makes the whole result that error.\n    [] => nums\n    for k in cells:\n        read_cell(k, vals, refs) => v\n        if v[0] == \"e\":\n            return v\n        if v[0] == \"n\" and not (len(vals) > 0 and vals[k][0] == \"t\"):\n            nums + [v[1]] => nums\n    if len(vals) == 0:\n        return [\"n\", [0, 1]]\n    if fname == \"SUM\" or fname == \"AVG\":\n        [0, 1] => s\n        for x in nums:\n            frac.plus(s, x) => s\n        if fname == \"SUM\":\n            return [\"n\", s]\n        if len(nums) == 0:\n            return [\"e\", \"#DIV/0\"]\n        return [\"n\", frac.over(s, [len(nums), 1])]\n    if len(nums) == 0:\n        return [\"n\", [0, 1]]\n    nums[0] => best\n    for x in nums:\n        if fname == \"MIN\" and frac.less(x, best):\n            x => best\n        if fname == \"MAX\" and frac.less(best, x):\n            x => best\n    return [\"n\", best]\n\ndef checked(text):\n    # Parses a formula (\"=\" already taken off): [problem or \"\", the cells it\n    # reads, the tokens].\n    tokens(text) => ts\n    if len(ts) > 0 and ts[0][0] == \"bad\":\n        return [\"it has \" + ts[0][1], [], []]\n    if len(ts) == 0:\n        return [\"it is empty\", [], []]\n    [0, \"\"] => p\n    [[]] => refs\n    expr(ts, p, [], refs)\n    if p[1] != \"\":\n        return [\"it has \" + p[1][1], [], []]\n    if p[0] < len(ts):\n        return [\"something is left over after the formula ends\", [], []]\n    return [\"\", refs[0], ts]\n\ndef value_of(ts, vals):\n    [0, \"\"] => p\n    [[]] => refs\n    return expr(ts, p, vals, refs)\n\ndef recalculated(cells):\n    # Every cell's value, and the formula cells in the order they were\n    # worked out. The order is Kahn's algorithm, as in the corpus case\n    # topological-sort: a formula is ready once every formula it reads is\n    # done; formulas never ready are caught in a cycle, or read one that is.\n    [] => vals\n    [] => formulas\n    for k in [0:53]:\n        cells[k] => s\n        if s == \"\":\n            vals + [[\"t\", \"\"]] => vals\n        elif s[0] == \"=\":\n            vals + [[\"e\", \"#CYCLE\"]] => vals\n            formulas + [k] => formulas\n        else:\n            frac.parse(s) => x\n            if len(x) > 0:\n                vals + [[\"n\", x]] => vals\n            else:\n                vals + [[\"t\", s]] => vals\n    # reads[k]: the formula cells formula k reads, once each\n    {} => reads\n    {} => waiting\n    for k in formulas:\n        checked(cells[k][1:len(cells[k])]) => c\n        [] => mine\n        for r in c[1]:\n            if cells[r] != \"\" and cells[r][0] == \"=\":\n                False => seen\n                for m in mine:\n                    if m == r:\n                        True => seen\n                if not seen:\n                    mine + [r] => mine\n        mine => reads[k]\n        len(mine) => waiting[k]\n    [] => ready\n    for k in formulas:\n        if waiting[k] == 0:\n            ready + [k] => ready\n    [] => order\n    0 => head\n    while head < len(ready):\n        ready[head] => k\n        head + 1 => head\n        order + [k] => order\n        tokens(cells[k][1:len(cells[k])]) => ts\n        value_of(ts, vals) => vals[k]\n        for j in formulas:\n            for r in reads[j]:\n                if r == k:\n                    waiting[j] - 1 => waiting[j]\n                    if waiting[j] == 0:\n                        ready + [j] => ready\n    return [vals, order]\n",
      "python": "import frac\n\ndef cell_name(k):\n    return \"ABCDEF\"[k % 6] + str(int(k / 6) + 1)\n\ndef cell_index(s):\n    if len(s) != 2:\n        return -1\n    col = -1\n    for c in range(0, 6):\n        if s[0] == \"ABCDEF\"[c] or s[0] == \"abcdef\"[c]:\n            col = c\n    if col == -1 or not (s[1] >= \"1\" and s[1] <= \"9\"):\n        return -1\n    return (int(s[1]) - 1) * 6 + col\n\ndef tokens(f):\n    out = []\n    i = 0\n    while i < len(f):\n        c = f[i]\n        if c == \" \":\n            i = i + 1\n        elif c == \"+\" or c == \"-\" or c == \"*\" or c == \"/\" or c == \"(\" or c == \")\" or c == \":\" or c == \",\":\n            out = out + [[\"op\", c]]\n            i = i + 1\n        elif c >= \"0\" and c <= \"9\" or c == \".\":\n            j = i\n            while j < len(f) and (f[j] >= \"0\" and f[j] <= \"9\" or f[j] == \".\"):\n                j = j + 1\n            x = frac.parse(f[i:j])\n            if len(x) == 0:\n                return [[\"bad\", \"the number \" + f[i:j]]]\n            out = out + [[\"num\", x]]\n            i = j\n        elif c >= \"A\" and c <= \"Z\" or c >= \"a\" and c <= \"z\":\n            j = i\n            while j < len(f) and (f[j] >= \"A\" and f[j] <= \"Z\" or f[j] >= \"a\" and f[j] <= \"z\" or f[j] >= \"0\" and f[j] <= \"9\"):\n                j = j + 1\n            w = f[i:j]\n            k = cell_index(w)\n            if k >= 0:\n                out = out + [[\"cell\", k]]\n            else:\n                up = \"\"\n                for ch in w:\n                    u = ch\n                    for q in range(0, 26):\n                        if ch == \"abcdefghijklmnopqrstuvwxyz\"[q]:\n                            u = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"[q]\n                    up = up + u\n                if up == \"SUM\" or up == \"MIN\" or up == \"MAX\" or up == \"AVG\":\n                    out = out + [[\"fn\", up]]\n                else:\n                    return [[\"bad\", w + \", which is not a cell from A1 to F9 or one of SUM, MIN, MAX, AVG\"]]\n            i = j\n        else:\n            return [[\"bad\", \"the character \" + c]]\n    return out\n\ndef peek(ts, p):\n    if p[0] < len(ts):\n        return ts[p[0]]\n    return [\"end\", \"\"]\n\ndef is_op(t, c):\n    return t[0] == \"op\" and t[1] == c\n\ndef arith(a, b, op):\n    if a[0] == \"e\":\n        return a\n    if b[0] == \"e\":\n        return b\n    if a[0] == \"t\" or b[0] == \"t\":\n        return [\"e\", \"#TEXT\"]\n    if op == \"+\":\n        return [\"n\", frac.plus(a[1], b[1])]\n    if op == \"-\":\n        return [\"n\", frac.minus(a[1], b[1])]\n    if op == \"*\":\n        return [\"n\", frac.times(a[1], b[1])]\n    if frac.is_zero(b[1]):\n        return [\"e\", \"#DIV/0\"]\n    return [\"n\", frac.over(a[1], b[1])]\n\ndef read_cell(k, vals, refs):\n    refs[0] = refs[0] + [k]\n    if len(vals) == 0:\n        return [\"n\", [0, 1]]\n    v = vals[k]\n    if v[0] == \"t\" and v[1] == \"\":\n        return [\"n\", [0, 1]]\n    return v\n\ndef expr(ts, p, vals, refs):\n    v = term(ts, p, vals, refs)\n    while is_op(peek(ts, p), \"+\") or is_op(peek(ts, p), \"-\"):\n        op = peek(ts, p)[1]\n        p[0] = p[0] + 1\n        v = arith(v, term(ts, p, vals, refs), op)\n    return v\n\ndef term(ts, p, vals, refs):\n    v = factor(ts, p, vals, refs)\n    while is_op(peek(ts, p), \"*\") or is_op(peek(ts, p), \"/\"):\n        op = peek(ts, p)[1]\n        p[0] = p[0] + 1\n        v = arith(v, factor(ts, p, vals, refs), op)\n    return v\n\ndef factor(ts, p, vals, refs):\n    t = peek(ts, p)\n    if is_op(t, \"-\"):\n        p[0] = p[0] + 1\n        return arith([\"n\", [0, 1]], factor(ts, p, vals, refs), \"-\")\n    if t[0] == \"num\":\n        p[0] = p[0] + 1\n        return [\"n\", t[1]]\n    if t[0] == \"cell\":\n        p[0] = p[0] + 1\n        return read_cell(t[1], vals, refs)\n    if is_op(t, \"(\"):\n        p[0] = p[0] + 1\n        v = expr(ts, p, vals, refs)\n        if not is_op(peek(ts, p), \")\"):\n            p[1] = [\"bad\", \"a ( without its )\"]\n        else:\n            p[0] = p[0] + 1\n        return v\n    if t[0] == \"fn\":\n        p[0] = p[0] + 1\n        return function(t[1], ts, p, vals, refs)\n    p[1] = [\"bad\", \"something missing where a number, a cell or ( should be\"]\n    return [\"e\", \"#BAD\"]\n\ndef function(fname, ts, p, vals, refs):\n    if not is_op(peek(ts, p), \"(\"):\n        p[1] = [\"bad\", fname + \" without ( after it\"]\n        return [\"e\", \"#BAD\"]\n    p[0] = p[0] + 1\n    cells = []\n    while True:\n        a = peek(ts, p)\n        if a[0] != \"cell\":\n            p[1] = [\"bad\", fname + \"( with something other than cells and ranges in it, where \" + fname + \"(A1:A5) would do\"]\n            return [\"e\", \"#BAD\"]\n        p[0] = p[0] + 1\n        if is_op(peek(ts, p), \":\"):\n            p[0] = p[0] + 1\n            b = peek(ts, p)\n            if b[0] != \"cell\":\n                p[1] = [\"bad\", \"a range with no cell after its :\"]\n                return [\"e\", \"#BAD\"]\n            p[0] = p[0] + 1\n            c1 = a[1] % 6\n            c2 = b[1] % 6\n            r1 = int(a[1] / 6)\n            r2 = int(b[1] / 6)\n            if c2 < c1:\n                t = c1\n                c1 = c2\n                c2 = t\n            if r2 < r1:\n                t = r1\n                r1 = r2\n                r2 = t\n            for r in range(r1, r2+1):\n                for c in range(c1, c2+1):\n                    cells = cells + [r * 6 + c]\n        else:\n            cells = cells + [a[1]]\n        if is_op(peek(ts, p), \",\"):\n            p[0] = p[0] + 1\n        elif is_op(peek(ts, p), \")\"):\n            p[0] = p[0] + 1\n            break\n        else:\n            p[1] = [\"bad\", fname + \"( with no , between two cells or no ) at the end\"]\n            return [\"e\", \"#BAD\"]\n    nums = []\n    for k in cells:\n        v = read_cell(k, vals, refs)\n        if v[0] == \"e\":\n            return v\n        if v[0] == \"n\" and not (len(vals) > 0 and vals[k][0] == \"t\"):\n            nums = nums + [v[1]]\n    if len(vals) == 0:\n        return [\"n\", [0, 1]]\n    if fname == \"SUM\" or fname == \"AVG\":\n        s = [0, 1]\n        for x in nums:\n            s = frac.plus(s, x)\n        if fname == \"SUM\":\n            return [\"n\", s]\n        if len(nums) == 0:\n            return [\"e\", \"#DIV/0\"]\n        return [\"n\", frac.over(s, [len(nums), 1])]\n    if len(nums) == 0:\n        return [\"n\", [0, 1]]\n    best = nums[0]\n    for x in nums:\n        if fname == \"MIN\" and frac.less(x, best):\n            best = x\n        if fname == \"MAX\" and frac.less(best, x):\n            best = x\n    return [\"n\", best]\n\ndef checked(text):\n    ts = tokens(text)\n    if len(ts) > 0 and ts[0][0] == \"bad\":\n        return [\"it has \" + ts[0][1], [], []]\n    if len(ts) == 0:\n        return [\"it is empty\", [], []]\n    p = [0, \"\"]\n    refs = [[]]\n    expr(ts, p, [], refs)\n    if p[1] != \"\":\n        return [\"it has \" + p[1][1], [], []]\n    if p[0] < len(ts):\n        return [\"something is left over after the formula ends\", [], []]\n    return [\"\", refs[0], ts]\n\ndef value_of(ts, vals):\n    p = [0, \"\"]\n    refs = [[]]\n    return expr(ts, p, vals, refs)\n\ndef recalculated(cells):\n    vals = []\n    formulas = []\n    for k in range(0, 54):\n        s = cells[k]\n        if s == \"\":\n            vals = vals + [[\"t\", \"\"]]\n        elif s[0] == \"=\":\n            vals = vals + [[\"e\", \"#CYCLE\"]]\n            formulas = formulas + [k]\n        else:\n            x = frac.parse(s)\n            if len(x) > 0:\n                vals = vals + [[\"n\", x]]\n            else:\n                vals = vals + [[\"t\", s]]\n    reads = {}\n    waiting = {}\n    for k in formulas:\n        c = checked(cells[k][1:len(cells[k])])\n        mine = []\n        for r in c[1]:\n            if cells[r] != \"\" and cells[r][0] == \"=\":\n                seen = False\n                for m in mine:\n                    if m == r:\n                        seen = True\n                if not seen:\n                    mine = mine + [r]\n        reads[k] = mine\n        waiting[k] = len(mine)\n    ready = []\n    for k in formulas:\n        if waiting[k] == 0:\n            ready = ready + [k]\n    order = []\n    head = 0\n    while head < len(ready):\n        k = ready[head]\n        head = head + 1\n        order = order + [k]\n        ts = tokens(cells[k][1:len(cells[k])])\n        vals[k] = value_of(ts, vals)\n        for j in formulas:\n            for r in reads[j]:\n                if r == k:\n                    waiting[j] = waiting[j] - 1\n                    if waiting[j] == 0:\n                        ready = ready + [j]\n    return [vals, order]\n"
    },
    {
      "name": "frac.eml",
      "eml": "# P056 mini spreadsheet - exact fractions, from P007 (calculator) by way of\n# P038 and P044.\n# A number is a list [n, d]: n / d in lowest terms with d > 0. Integers have\n# no size limit, but EML has no //, and a / b goes through a float that cannot\n# hold a large quotient exactly, so whole-number division is written out.\n\ndef quotient(a, b):\n    # a // b for whole numbers a >= 0 and b > 0, exact at any size. Long\n    # division by doubling: take away the largest b * 2^k that still fits.\n    0 => q\n    while a >= b:\n        b => m\n        1 => k\n        while m + m <= a:\n            m + m => m\n            k + k => k\n        a - m => a\n        q + k => q\n    return q\n\ndef gcd(a, b):\n    while b != 0:\n        a % b => r\n        b => a\n        r => b\n    return a\n\ndef make(n, d):\n    # n / d in lowest terms with a positive denominator (d != 0).\n    if d < 0:\n        0 - n => n\n        0 - d => d\n    abs(n) => a\n    gcd(a, d) => g\n    if n < 0:\n        return [0 - quotient(a, g), quotient(d, g)]\n    return [quotient(a, g), quotient(d, g)]\n\ndef plus(x, y):\n    return make(x[0] * y[1] + y[0] * x[1], x[1] * y[1])\n\ndef minus(x, y):\n    return make(x[0] * y[1] - y[0] * x[1], x[1] * y[1])\n\ndef times(x, y):\n    return make(x[0] * y[0], x[1] * y[1])\n\ndef over(x, y):\n    # x / y; the caller has checked that y is not zero.\n    return make(x[0] * y[1], x[1] * y[0])\n\ndef is_zero(x):\n    return x[0] == 0\n\ndef digits_value(s):\n    # The value of a string of 1 to 9 digits, otherwise -1.\n    if s == \"\" or len(s) > 9:\n        return -1\n    0 => n\n    for c in s:\n        if not (c in \"0123456789\"):\n            return -1\n        n * 10 + int(c) => n\n    return n\n\ndef parse(s):\n    # \"3\", \"-2\", \"3/4\", \"-0.25\" as a fraction; [] if it is not a number.\n    1 => sign\n    if len(s) > 0 and s[0] == \"-\":\n        -1 => sign\n        s[1:len(s)] => s\n    0 => k\n    while k < len(s) and s[k] != \"/\" and s[k] != \".\":\n        k + 1 => k\n    if k == len(s):\n        digits_value(s) => n\n        if n < 0:\n            return []\n        return make(sign * n, 1)\n    digits_value(s[0:k]) => whole\n    s[k + 1:len(s)] => rest\n    digits_value(rest) => part\n    if whole < 0 or part < 0:\n        return []\n    if s[k] == \"/\":\n        if part == 0:\n            return []\n        return make(sign * whole, part)\n    # a decimal point: 0.25 is 25 / 100\n    1 => scale\n    for c in rest:\n        scale * 10 => scale\n    return make(sign * (whole * scale + part), scale)\n\ndef less(x, y):\n    return x[0] * y[1] < y[0] * x[1]\n\ndef decimal(x, places):\n    # x to the given number of decimal places, halves rounded away from zero,\n    # with \"about \" in front when the value does not end there exactly.\n    \"\" => sign\n    abs(x[0]) => n\n    if x[0] < 0:\n        \"-\" => sign\n    1 => scale\n    for k in [1:places]:\n        scale * 10 => scale\n    quotient(2 * n * scale + x[1], 2 * x[1]) => t\n    \"\" => about\n    if (n * scale) % x[1] != 0:\n        \"about \" => about\n    str(quotient(t, scale)) => whole\n    str(t % scale) => part\n    while len(part) < places:\n        \"0\" + part => part\n    # drop trailing zeros of an exact value\n    if about == \"\":\n        while len(part) > 0 and part[len(part) - 1] == \"0\":\n            part[0:len(part) - 1] => part\n    if t == 0:\n        \"\" => sign\n    if part == \"\":\n        return about + sign + whole\n    return about + sign + whole + \".\" + part\n\ndef text(x):\n    # \"3\", \"-3\", \"3/4\", \"-3/4\".\n    if x[1] == 1:\n        return str(x[0])\n    return str(x[0]) + \"/\" + str(x[1])\n",
      "python": "def quotient(a, b):\n    q = 0\n    while a >= b:\n        m = b\n        k = 1\n        while m + m <= a:\n            m = m + m\n            k = k + k\n        a = a - m\n        q = q + k\n    return q\n\ndef gcd(a, b):\n    while b != 0:\n        r = a % b\n        a = b\n        b = r\n    return a\n\ndef make(n, d):\n    if d < 0:\n        n = 0 - n\n        d = 0 - d\n    a = abs(n)\n    g = gcd(a, d)\n    if n < 0:\n        return [0 - quotient(a, g), quotient(d, g)]\n    return [quotient(a, g), quotient(d, g)]\n\ndef plus(x, y):\n    return make(x[0] * y[1] + y[0] * x[1], x[1] * y[1])\n\ndef minus(x, y):\n    return make(x[0] * y[1] - y[0] * x[1], x[1] * y[1])\n\ndef times(x, y):\n    return make(x[0] * y[0], x[1] * y[1])\n\ndef over(x, y):\n    return make(x[0] * y[1], x[1] * y[0])\n\ndef is_zero(x):\n    return x[0] == 0\n\ndef digits_value(s):\n    if s == \"\" or len(s) > 9:\n        return -1\n    n = 0\n    for c in s:\n        if not c in \"0123456789\":\n            return -1\n        n = n * 10 + int(c)\n    return n\n\ndef parse(s):\n    sign = 1\n    if len(s) > 0 and s[0] == \"-\":\n        sign = -1\n        s = s[1:len(s)]\n    k = 0\n    while k < len(s) and s[k] != \"/\" and s[k] != \".\":\n        k = k + 1\n    if k == len(s):\n        n = digits_value(s)\n        if n < 0:\n            return []\n        return make(sign * n, 1)\n    whole = digits_value(s[0:k])\n    rest = s[k + 1:len(s)]\n    part = digits_value(rest)\n    if whole < 0 or part < 0:\n        return []\n    if s[k] == \"/\":\n        if part == 0:\n            return []\n        return make(sign * whole, part)\n    scale = 1\n    for c in rest:\n        scale = scale * 10\n    return make(sign * (whole * scale + part), scale)\n\ndef less(x, y):\n    return x[0] * y[1] < y[0] * x[1]\n\ndef decimal(x, places):\n    sign = \"\"\n    n = abs(x[0])\n    if x[0] < 0:\n        sign = \"-\"\n    scale = 1\n    for k in range(1, places+1):\n        scale = scale * 10\n    t = quotient(2 * n * scale + x[1], 2 * x[1])\n    about = \"\"\n    if n * scale % x[1] != 0:\n        about = \"about \"\n    whole = str(quotient(t, scale))\n    part = str(t % scale)\n    while len(part) < places:\n        part = \"0\" + part\n    if about == \"\":\n        while len(part) > 0 and part[len(part) - 1] == \"0\":\n            part = part[0:len(part) - 1]\n    if t == 0:\n        sign = \"\"\n    if part == \"\":\n        return about + sign + whole\n    return about + sign + whole + \".\" + part\n\ndef text(x):\n    if x[1] == 1:\n        return str(x[0])\n    return str(x[0]) + \"/\" + str(x[1])\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "0\nx\n1\nG1\nA0\n\n1\nA1\n\n1\nA1\n=1+\n1\nA1\n=SUM(B1\n1\nA1\n=A1+1\n1\nA2\n=B1/C1\n1\nA3\nlabel\n1\nA4\n=A3*2\n1\nA5\n=SUM(A3:A4, B9)\n1\nA6\n=1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1\n1\nA6\n=AVG(B1:B9)\n2\nA1\n3\n4\n5\n7\n",
      "screen": "== Mini spreadsheet ==\nCells A1 to F9 hold numbers, labels, or formulas such as =B2*C2 or =SUM(D2:D4).\n\n1) set a cell  2) empty a cell  3) values  4) formulas  5) order  6) sample  7) quit\nchoice> 0\nPick a number from 1 to 7.\n\n1) set a cell  2) empty a cell  3) values  4) formulas  5) order  6) sample  7) quit\nchoice> x\nPick a number from 1 to 7.\n\n1) set a cell  2) empty a cell  3) values  4) formulas  5) order  6) sample  7) quit\nchoice> 1\ncell> G1\nType a cell from A1 to F9, like B3.\ncell> A0\nType a cell from A1 to F9, like B3.\ncell> \nCancelled.\n\n1) set a cell  2) empty a cell  3) values  4) formulas  5) order  6) sample  7) quit\nchoice> 1\ncell> A1\ncontent of A1 (a number, a label, or =formula)> \nCancelled - use 2) to empty a cell.\n\n1) set a cell  2) empty a cell  3) values  4) formulas  5) order  6) sample  7) quit\nchoice> 1\ncell> A1\ncontent of A1 (a number, a label, or =formula)> =1+\nNot a formula: it has something missing where a number, a cell or ( should be. Nothing was changed.\n\n1) set a cell  2) empty a cell  3) values  4) formulas  5) order  6) sample  7) quit\nchoice> 1\ncell> A1\ncontent of A1 (a number, a label, or =formula)> =SUM(B1\nNot a formula: it has SUM( with no , between two cells or no ) at the end. Nothing was changed.\n\n1) set a cell  2) empty a cell  3) values  4) formulas  5) order  6) sample  7) quit\nchoice> 1\ncell> A1\ncontent of A1 (a number, a label, or =formula)> =A1+1\nA1 = #CYCLE; 0 formula cell(s) worked out again.\n1 formula cell(s) are in a cycle, or read one - they show #CYCLE until it is broken.\n\n1) set a cell  2) empty a cell  3) values  4) formulas  5) order  6) sample  7) quit\nchoice> 1\ncell> A2\ncontent of A2 (a number, a label, or =formula)> =B1/C1\nA2 = #DIV/0; 1 formula cell(s) worked out again.\n1 formula cell(s) are in a cycle, or read one - they show #CYCLE until it is broken.\n\n1) set a cell  2) empty a cell  3) values  4) formulas  5) order  6) sample  7) quit\nchoice> 1\ncell> A3\ncontent of A3 (a number, a label, or =formula)> label\nA3 = label; 1 formula cell(s) worked out again.\n1 formula cell(s) are in a cycle, or read one - they show #CYCLE until it is broken.\n\n1) set a cell  2) empty a cell  3) values  4) formulas  5) order  6) sample  7) quit\nchoice> 1\ncell> A4\ncontent of A4 (a number, a label, or =formula)> =A3*2\nA4 = #TEXT; 2 formula cell(s) worked out again.\n1 formula cell(s) are in a cycle, or read one - they show #CYCLE until it is broken.\n\n1) set a cell  2) empty a cell  3) values  4) formulas  5) order  6) sample  7) quit\nchoice> 1\ncell> A5\ncontent of A5 (a number, a label, or =formula)> =SUM(A3:A4, B9)\nA5 = #TEXT; 3 formula cell(s) worked out again.\n1 formula cell(s) are in a cycle, or read one - they show #CYCLE until it is broken.\n\n1) set a cell  2) empty a cell  3) values  4) formulas  5) order  6) sample  7) quit\nchoice> 1\ncell> A6\ncontent of A6 (a number, a label, or =formula)> =1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1\nKeep a cell to 60 characters.\n\n1) set a cell  2) empty a cell  3) values  4) formulas  5) order  6) sample  7) quit\nchoice> 1\ncell> A6\ncontent of A6 (a number, a label, or =formula)> =AVG(B1:B9)\nA6 = #DIV/0; 4 formula cell(s) worked out again.\n1 formula cell(s) are in a cycle, or read one - they show #CYCLE until it is broken.\n\n1) set a cell  2) empty a cell  3) values  4) formulas  5) order  6) sample  7) quit\nchoice> 2\ncell to empty> A1\nA1 is empty now; 4 formula cell(s) worked out again.\n\n1) set a cell  2) empty a cell  3) values  4) formulas  5) order  6) sample  7) quit\nchoice> 3\n              A          B          C          D          E          F\n 1\n 2       #DIV/0\n 3   label\n 4        #TEXT\n 5        #TEXT\n 6       #DIV/0\n 7\n 8\n 9\n\n1) set a cell  2) empty a cell  3) values  4) formulas  5) order  6) sample  7) quit\nchoice> 4\n              A          B          C          D          E          F\n 1\n 2   =B1/C1\n 3   label\n 4   =A3*2\n 5   =SUM(A3:A~\n 6   =AVG(B1:B~\n 7\n 8\n 9\n\n1) set a cell  2) empty a cell  3) values  4) formulas  5) order  6) sample  7) quit\nchoice> 5\nWorked out in this order: A2, A4, A6, A5.\n\n1) set a cell  2) empty a cell  3) values  4) formulas  5) order  6) sample  7) quit\nchoice> 7\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "6\n4\n5\n1\nB3\n1.25\n1\nB7\n=D8/100\n5\n3\n1\nB7\n0.1\n3\n7\n",
      "screen": "== Mini spreadsheet ==\nCells A1 to F9 hold numbers, labels, or formulas such as =B2*C2 or =SUM(D2:D4).\n\n1) set a cell  2) empty a cell  3) values  4) formulas  5) order  6) sample  7) quit\nchoice> 6\nA small order: three items, their totals, tax at 8% and an average.\n              A          B          C          D          E          F\n 1   Item       Price      Qty        Total\n 2   Paper             4.5          3       13.5\n 3   Pens              1.2         10         12\n 4   Folders          2.75          4         11\n 5\n 6   Subtotal                               36.5\n 7   Tax rate         0.08                  2.92\n 8   Total                                 39.42\n 9   Average                              ~12.17\n\n1) set a cell  2) empty a cell  3) values  4) formulas  5) order  6) sample  7) quit\nchoice> 4\n              A          B          C          D          E          F\n 1   Item       Price      Qty        Total\n 2   Paper      4.5        3          =B2*C2\n 3   Pens       1.2        10         =B3*C3\n 4   Folders    2.75       4          =B4*C4\n 5\n 6   Subtotal                         =SUM(D2:D~\n 7   Tax rate   0.08                  =D6*B7\n 8   Total                            =D6+D7\n 9   Average                          =AVG(D2:D~\n\n1) set a cell  2) empty a cell  3) values  4) formulas  5) order  6) sample  7) quit\nchoice> 5\nWorked out in this order: D2, D3, D4, D6, D9, D7, D8.\n\n1) set a cell  2) empty a cell  3) values  4) formulas  5) order  6) sample  7) quit\nchoice> 1\ncell> B3\ncontent of B3 (a number, a label, or =formula)> 1.25\nB3 = 1.25; 7 formula cell(s) worked out again.\n\n1) set a cell  2) empty a cell  3) values  4) formulas  5) order  6) sample  7) quit\nchoice> 1\ncell> B7\ncontent of B7 (a number, a label, or =formula)> =D8/100\nB7 = #CYCLE; 5 formula cell(s) worked out again.\n3 formula cell(s) are in a cycle, or read one - they show #CYCLE until it is broken.\n\n1) set a cell  2) empty a cell  3) values  4) formulas  5) order  6) sample  7) quit\nchoice> 5\nWorked out in this order: D2, D3, D4, D6, D9.\nIn a cycle, or reading one: B7, D7, D8.\n\n1) set a cell  2) empty a cell  3) values  4) formulas  5) order  6) sample  7) quit\nchoice> 3\n              A          B          C          D          E          F\n 1   Item       Price      Qty        Total\n 2   Paper             4.5          3       13.5\n 3   Pens             1.25         10       12.5\n 4   Folders          2.75          4         11\n 5\n 6   Subtotal                                 37\n 7   Tax rate       #CYCLE                #CYCLE\n 8   Total                                #CYCLE\n 9   Average                              ~12.33\n\n1) set a cell  2) empty a cell  3) values  4) formulas  5) order  6) sample  7) quit\nchoice> 1\ncell> B7\ncontent of B7 (a number, a label, or =formula)> 0.1\nB7 = 0.1; 7 formula cell(s) worked out again.\n\n1) set a cell  2) empty a cell  3) values  4) formulas  5) order  6) sample  7) quit\nchoice> 3\n              A          B          C          D          E          F\n 1   Item       Price      Qty        Total\n 2   Paper             4.5          3       13.5\n 3   Pens             1.25         10       12.5\n 4   Folders          2.75          4         11\n 5\n 6   Subtotal                                 37\n 7   Tax rate          0.1                   3.7\n 8   Total                                  40.7\n 9   Average                              ~12.33\n\n1) set a cell  2) empty a cell  3) values  4) formulas  5) order  6) sample  7) quit\nchoice> 7\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "topological-sort",
      "caseId": "107-topological-sort",
      "title": "Topological sort (Kahn's algorithm)"
    }
  ],
  "updated": "2026-10-10"
}
