{
  "id": "P089",
  "slug": "fixed-width-report",
  "key": "P089-fixed-width-report",
  "title": "Fixed-width report",
  "summary": "Payroll lines in fixed columns, shown under a ruler: every line's length checked before its columns are trusted, every error reported at once (and what stopping at the first would have said), and pay added up by department in whole cents.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P089 - Fixed-width report\n\nPayroll lines in fixed columns, shown under a column ruler. Each line's\nlength is checked before its columns are trusted, every error is reported\nat once - with what stopping at the first error would have said - and pay\nis added up by department in whole cents. It starts with nine lines, five\nof them bad.\n\n- `main.eml` - the menu, the ruler, the checks, the report, adding and\n  removing lines\n- `records.eml` - the columns, the field checks, the check of all lines,\n  the first error, and pay\n\nHow each part works:\n\n- A line is exactly 42 characters: id in columns 0-6 (six digits), name\n  6-26 (letters, spaces, - ' and .), department 26-30 (four capital\n  letters), hours 30-35 (like 037.5, at most 168.0) and rate 35-42 (like\n  0025.50, more than 0).\n- As the corpus case `fixed-width-record-parser` warns, a short line still\n  slices without complaint - into wrong fields. So the length is checked\n  first, and a line of the wrong length is reported on its own; its\n  columns are not read at all.\n- As in the corpus case `schema-validator-all-errors`, every error of every\n  line is listed, not just the first: one round of fixes instead of one\n  per error. The program also shows the single error that stopping at the\n  first would have reported. A repeated id is an error on the later line.\n- Pay is hours (in tenths) x rate (in cents) / 10, rounded half up to the\n  cent: 42.5 h x 31.75 = 1,349.375, paid as 1,349.38. Only good lines are\n  paid, by department and in all.\n\nWhat is checked: menu choices 1 to 6; a new line of any text (it is added,\nwith its errors listed); a line number that exists; at most 20 lines. An\nempty answer cancels.\n\nSessions: `sessions/basic.in` gives:\n- the nine lines under the ruler, and their seven errors on five lines;\n- the pay report: SALE 1,845.00, TECH 2,466.58, 4,311.58 in all;\n- a good line added for Jo Kim and paid; the short line removed, leaving\n  six errors on four lines.\n\n`sessions/bad-input.in` gives:\n- menu choices 0 and x;\n- \"abc\" added (3 characters), and a line with five errors in one go;\n- line numbers 0, 99 and x to remove, then cancels;\n- the check of all eleven lines: 13 errors on 7 of them.\n\nBuilt on the verified corpus cases `fixed-width-record-parser` (check the\nline before trusting its slices) and `schema-validator-all-errors` (every\nfailure at once, against stopping at the first).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P089 fixed-width report: payroll lines in fixed columns - shown under a\n# column ruler, checked with every error listed (and what stopping at the\n# first would have said), and paid out by department in whole cents.\nimport records\n\ndef money(c):\n    0 => w\n    c => r\n    while r >= 100:\n        r - 100 => r\n        w + 1 => w\n    \"\" => z\n    if r < 10:\n        \"0\" => z\n    # thousands\n    str(w) => s\n    \"\" => g\n    0 => k\n    len(s) - 1 => i\n    while i >= 0:\n        if k > 0 and k % 3 == 0:\n            \",\" + g => g\n        s[i] + g => g\n        k + 1 => k\n        i - 1 => i\n    return g + \".\" + z + str(r)\n\ndef lpad(s, n):\n    while len(s) < n:\n        \" \" + s => s\n    return s\n\ndef show(lines):\n    \"    \" + \"          1         2         3         4\" => top\n    top ^0\n    \"    012345678901234567890123456789012345678901\" ^0\n    for i in [0:len(lines) - 1]:\n        (lpad(str(i + 1), 2) + \"  \" + lines[i]) ^0\n    \"    id    name                dept hours rate\" ^0\n\ndef check_all(lines):\n    records.check(lines) => all\n    0 => bad\n    0 => count\n    for i in [0:len(all) - 1]:\n        if len(all[i]) > 0:\n            bad + 1 => bad\n            for e in all[i]:\n                (\"  line \" + str(i + 1) + \": \" + e) ^0\n                count + 1 => count\n    if bad == 0:\n        (\"All \" + str(len(lines)) + \" lines are good.\") ^0\n        return\n    (\"Checking everything: \" + str(count) + \" errors on \" + str(bad) + \" of the \" + str(len(lines)) + \" lines.\") ^0\n    records.first_error(lines) => f\n    (\"Stopping at the first would have said only: line \" + str(f[0] + 1) + \": \" + f[1] + \" - and the next run would find the next one.\") ^0\n\ndef report(lines):\n    records.check(lines) => all\n    [] => depts\n    [] => sums\n    0 => total\n    0 => used\n    for i in [0:len(lines) - 1]:\n        if len(all[i]) == 0:\n            lines[i] => l\n            records.pay(l) => p\n            (lpad(str(i + 1), 2) + \"  \" + records.trim_right(l[6:26]) + \", \" + l[26:30] + \": \" + l[30:35] + \" h x \" + l[35:42] + \" = \" + money(p)) ^0\n            -1 => k\n            for j in [0:len(depts) - 1]:\n                if depts[j] == l[26:30]:\n                    j => k\n            if k == -1:\n                depts + [l[26:30]] => depts\n                sums + [p] => sums\n            else:\n                sums[k] + p => sums[k]\n            total + p => total\n            used + 1 => used\n    if used == 0:\n        \"No good lines to pay.\" ^0\n        return\n    for j in [0:len(depts) - 1]:\n        (\"  \" + depts[j] + \"  \" + lpad(money(sums[j]), 12)) ^0\n    (\"  all   \" + lpad(money(total), 12) + \"  from \" + str(used) + \" good lines; \" + str(len(lines) - used) + \" left out until they are fixed\") ^0\n\ndef add(lines):\n    if len(lines) == 20:\n        \"There are 20 lines already.\" ^0\n        return lines\n    input(\"new line (42 characters, as on the ruler)> \") => s\n    if s == \"\":\n        \"Cancelled.\" ^0\n        return lines\n    records.check(lines + [s]) => all\n    all[len(all) - 1] => errs\n    if len(errs) == 0:\n        \"Added; it is good.\" ^0\n    else:\n        \"errors\" => w\n        if len(errs) == 1:\n            \"error\" => w\n        (\"Added, with \" + str(len(errs)) + \" \" + w + \":\") ^0\n        for e in errs:\n            (\"  \" + e) ^0\n    return lines + [s]\n\ndef remove(lines):\n    input(\"line number to remove> \") => s\n    if s == \"\":\n        \"Cancelled.\" ^0\n        return lines\n    0 => n\n    True => ok\n    if len(s) > 2:\n        False => ok\n    for c in s:\n        if c >= \"0\" and c <= \"9\":\n            n * 10 + int(c) => n\n        else:\n            False => ok\n    if not ok or n < 1 or n > len(lines):\n        (\"There is no line \" + s + \".\") ^0\n        return lines\n    [] => out\n    for i in [0:len(lines) - 1]:\n        if i != n - 1:\n            out + [lines[i]] => out\n    (\"Removed line \" + str(n) + \".\") ^0\n    return out\n\n[\n    \"100001Ana Lopez           SALE040.00025.50\",\n    \"100002Ben O'Hara          SALE037.50022.00\",\n    \"100003Chloe Dubois        TECH042.50031.75\",\n    \"100004Dev Patel           TECH038.00029.40\",\n    \"10000XEli Moss            tech040.00024.00\",\n    \"100006Fay Lin             SALE170.00025.00\",\n    \"100007Gus Young           OPS 035.00000.00\",\n    \"100002Hana Ito            TECH036.00027.50\",\n    \"100009Ivo Novak           SALE040.0002\",\n] => lines\n\n\"== Fixed-width report ==\" ^0\n\"Payroll lines in fixed columns: checked before they are trusted, every error at once.\" ^0\nTrue => running\nwhile running:\n    \"\" ^0\n    \"1) lines  2) check  3) pay report  4) add a line  5) remove a line  6) quit\" ^0\n    input(\"choice> \") => choice\n    if choice == \"1\":\n        show(lines)\n    elif choice == \"2\":\n        check_all(lines)\n    elif choice == \"3\":\n        report(lines)\n    elif choice == \"4\":\n        add(lines) => lines\n    elif choice == \"5\":\n        remove(lines) => lines\n    elif choice == \"6\":\n        False => running\n    else:\n        \"Pick a number from 1 to 6.\" ^0\n\"Bye.\" ^0\n",
      "python": "import records\n\ndef money(c):\n    w = 0\n    r = c\n    while r >= 100:\n        r = r - 100\n        w = w + 1\n    z = \"\"\n    if r < 10:\n        z = \"0\"\n    s = str(w)\n    g = \"\"\n    k = 0\n    i = len(s) - 1\n    while i >= 0:\n        if k > 0 and k % 3 == 0:\n            g = \",\" + g\n        g = s[i] + g\n        k = k + 1\n        i = i - 1\n    return g + \".\" + z + str(r)\n\ndef lpad(s, n):\n    while len(s) < n:\n        s = \" \" + s\n    return s\n\ndef show(lines):\n    top = \"    \" + \"          1         2         3         4\"\n    print(top)\n    print(\"    012345678901234567890123456789012345678901\")\n    for i in range(0, len(lines)):\n        print(lpad(str(i + 1), 2) + \"  \" + lines[i])\n    print(\"    id    name                dept hours rate\")\n\ndef check_all(lines):\n    all = records.check(lines)\n    bad = 0\n    count = 0\n    for i in range(0, len(all)):\n        if len(all[i]) > 0:\n            bad = bad + 1\n            for e in all[i]:\n                print(\"  line \" + str(i + 1) + \": \" + e)\n                count = count + 1\n    if bad == 0:\n        print(\"All \" + str(len(lines)) + \" lines are good.\")\n        return\n    print(\"Checking everything: \" + str(count) + \" errors on \" + str(bad) + \" of the \" + str(len(lines)) + \" lines.\")\n    f = records.first_error(lines)\n    print(\"Stopping at the first would have said only: line \" + str(f[0] + 1) + \": \" + f[1] + \" - and the next run would find the next one.\")\n\ndef report(lines):\n    all = records.check(lines)\n    depts = []\n    sums = []\n    total = 0\n    used = 0\n    for i in range(0, len(lines)):\n        if len(all[i]) == 0:\n            l = lines[i]\n            p = records.pay(l)\n            print(lpad(str(i + 1), 2) + \"  \" + records.trim_right(l[6:26]) + \", \" + l[26:30] + \": \" + l[30:35] + \" h x \" + l[35:42] + \" = \" + money(p))\n            k = -1\n            for j in range(0, len(depts)):\n                if depts[j] == l[26:30]:\n                    k = j\n            if k == -1:\n                depts = depts + [l[26:30]]\n                sums = sums + [p]\n            else:\n                sums[k] = sums[k] + p\n            total = total + p\n            used = used + 1\n    if used == 0:\n        print(\"No good lines to pay.\")\n        return\n    for j in range(0, len(depts)):\n        print(\"  \" + depts[j] + \"  \" + lpad(money(sums[j]), 12))\n    print(\"  all   \" + lpad(money(total), 12) + \"  from \" + str(used) + \" good lines; \" + str(len(lines) - used) + \" left out until they are fixed\")\n\ndef add(lines):\n    if len(lines) == 20:\n        print(\"There are 20 lines already.\")\n        return lines\n    s = input(\"new line (42 characters, as on the ruler)> \")\n    if s == \"\":\n        print(\"Cancelled.\")\n        return lines\n    all = records.check(lines + [s])\n    errs = all[len(all) - 1]\n    if len(errs) == 0:\n        print(\"Added; it is good.\")\n    else:\n        w = \"errors\"\n        if len(errs) == 1:\n            w = \"error\"\n        print(\"Added, with \" + str(len(errs)) + \" \" + w + \":\")\n        for e in errs:\n            print(\"  \" + e)\n    return lines + [s]\n\ndef remove(lines):\n    s = input(\"line number to remove> \")\n    if s == \"\":\n        print(\"Cancelled.\")\n        return lines\n    n = 0\n    ok = True\n    if len(s) > 2:\n        ok = False\n    for c in s:\n        if c >= \"0\" and c <= \"9\":\n            n = n * 10 + int(c)\n        else:\n            ok = False\n    if not ok or n < 1 or n > len(lines):\n        print(\"There is no line \" + s + \".\")\n        return lines\n    out = []\n    for i in range(0, len(lines)):\n        if i != n - 1:\n            out = out + [lines[i]]\n    print(\"Removed line \" + str(n) + \".\")\n    return out\n\nlines = [\"100001Ana Lopez           SALE040.00025.50\", \"100002Ben O'Hara          SALE037.50022.00\", \"100003Chloe Dubois        TECH042.50031.75\", \"100004Dev Patel           TECH038.00029.40\", \"10000XEli Moss            tech040.00024.00\", \"100006Fay Lin             SALE170.00025.00\", \"100007Gus Young           OPS 035.00000.00\", \"100002Hana Ito            TECH036.00027.50\", \"100009Ivo Novak           SALE040.0002\"]\nprint(\"== Fixed-width report ==\")\nprint(\"Payroll lines in fixed columns: checked before they are trusted, every error at once.\")\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"1) lines  2) check  3) pay report  4) add a line  5) remove a line  6) quit\")\n    choice = input(\"choice> \")\n    if choice == \"1\":\n        show(lines)\n    elif choice == \"2\":\n        check_all(lines)\n    elif choice == \"3\":\n        report(lines)\n    elif choice == \"4\":\n        lines = add(lines)\n    elif choice == \"5\":\n        lines = remove(lines)\n    elif choice == \"6\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 6.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "records.eml",
      "eml": "# P089 fixed-width report - payroll lines in fixed columns, checked before\n# they are trusted, every error reported at once, and pay added up in whole\n# cents. The columns (start, end, end not included):\n#     id     0-6    six digits\n#     name   6-26   letters, spaces, - ' and .\n#     dept   26-30  four capital letters\n#     hours  30-35  like 037.5 (tenths of an hour, at most 168.0)\n#     rate   35-42  like 0025.50 (per hour, more than 0)\n# A line is exactly 42 characters.\n\n42 => WIDTH\n\ndef is_digits(s):\n    if len(s) == 0:\n        return False\n    for c in s:\n        if not (c >= \"0\" and c <= \"9\"):\n            return False\n    return True\n\ndef value(s):\n    0 => n\n    for c in s:\n        n * 10 + int(c) => n\n    return n\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 field_errors(line):\n    # Every problem with a line of the right length, as text.\n    [] => errs\n    line[0:6] => ident\n    line[6:26] => name\n    line[26:30] => dept\n    line[30:35] => hours\n    line[35:42] => rate\n    if not is_digits(ident):\n        errs + [\"id '\" + ident + \"' is not six digits\"] => errs\n    trim_right(name) => nm\n    True => ok_name\n    if nm == \"\" or name[0] == \" \":\n        False => ok_name\n    for c in nm:\n        if not ((c >= \"a\" and c <= \"z\") or (c >= \"A\" and c <= \"Z\") or c == \" \" or c == \"-\" or c == \"'\" or c == \".\"):\n            False => ok_name\n    if not ok_name:\n        errs + [\"name '\" + nm + \"' must start in column 6 and be letters, spaces, - ' or .\"] => errs\n    True => ok_dept\n    for c in dept:\n        if not (c >= \"A\" and c <= \"Z\"):\n            False => ok_dept\n    if not ok_dept:\n        errs + [\"dept '\" + dept + \"' is not four capital letters\"] => errs\n    if not (is_digits(hours[0:3]) and hours[3] == \".\" and is_digits(hours[4:5])):\n        errs + [\"hours '\" + hours + \"' is not like 037.5\"] => errs\n    elif value(hours[0:3]) * 10 + value(hours[4:5]) > 1680:\n        errs + [\"hours \" + hours + \" is more than the 168 in a week\"] => errs\n    if not (is_digits(rate[0:4]) and rate[4] == \".\" and is_digits(rate[5:7])):\n        errs + [\"rate '\" + rate + \"' is not like 0025.50\"] => errs\n    elif value(rate[0:4]) * 100 + value(rate[5:7]) == 0:\n        errs + [\"rate is 0\"] => errs\n    return errs\n\ndef check(lines):\n    # Fail-complete, as in the corpus case schema-validator-all-errors:\n    # every error of every line. A line of the wrong length is reported\n    # alone - its slices would not mean anything (the corpus case\n    # fixed-width-record-parser). A repeated id is an error on the later\n    # line. Returns, per line, its list of errors.\n    [] => out\n    [] => ids\n    for i in [0:len(lines) - 1]:\n        lines[i] => line\n        if len(line) != WIDTH:\n            out + [[\"is \" + str(len(line)) + \" characters, not \" + str(WIDTH) + \" - its columns cannot be trusted\"]] => out\n        else:\n            field_errors(line) => errs\n            for k in [0:len(ids) - 1]:\n                if ids[k][0] == line[0:6] and len(errs) == 0:\n                    errs + [\"id \" + line[0:6] + \" is already on line \" + str(ids[k][1] + 1)] => errs\n            if len(errs) == 0:\n                ids + [[line[0:6], i]] => ids\n            out + [errs] => out\n    return out\n\ndef first_error(lines):\n    # Fail-fast: the first error found, as [line, error], or [] if none.\n    check(lines) => all\n    for i in [0:len(all) - 1]:\n        if len(all[i]) > 0:\n            return [i, all[i][0]]\n    return []\n\ndef pay(line):\n    # hours (tenths) x rate (cents) / 10, rounded half up to the cent.\n    value(line[30:33]) * 10 + value(line[34:35]) => tenths\n    value(line[35:39]) * 100 + value(line[40:42]) => cents\n    tenths * cents => t\n    0 => q\n    t * 2 + 10 => n\n    while n >= 20:\n        20 => m\n        1 => k\n        while m + m <= n:\n            m + m => m\n            k + k => k\n        n - m => n\n        q + k => q\n    return q\n",
      "python": "WIDTH = 42\n\ndef is_digits(s):\n    if len(s) == 0:\n        return False\n    for c in s:\n        if not (c >= \"0\" and c <= \"9\"):\n            return False\n    return True\n\ndef value(s):\n    n = 0\n    for c in s:\n        n = n * 10 + int(c)\n    return n\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 field_errors(line):\n    errs = []\n    ident = line[0:6]\n    name = line[6:26]\n    dept = line[26:30]\n    hours = line[30:35]\n    rate = line[35:42]\n    if not is_digits(ident):\n        errs = errs + [\"id '\" + ident + \"' is not six digits\"]\n    nm = trim_right(name)\n    ok_name = True\n    if nm == \"\" or name[0] == \" \":\n        ok_name = False\n    for c in nm:\n        if not (c >= \"a\" and c <= \"z\" or c >= \"A\" and c <= \"Z\" or c == \" \" or c == \"-\" or c == \"'\" or c == \".\"):\n            ok_name = False\n    if not ok_name:\n        errs = errs + [\"name '\" + nm + \"' must start in column 6 and be letters, spaces, - ' or .\"]\n    ok_dept = True\n    for c in dept:\n        if not (c >= \"A\" and c <= \"Z\"):\n            ok_dept = False\n    if not ok_dept:\n        errs = errs + [\"dept '\" + dept + \"' is not four capital letters\"]\n    if not (is_digits(hours[0:3]) and hours[3] == \".\" and is_digits(hours[4:5])):\n        errs = errs + [\"hours '\" + hours + \"' is not like 037.5\"]\n    elif value(hours[0:3]) * 10 + value(hours[4:5]) > 1680:\n        errs = errs + [\"hours \" + hours + \" is more than the 168 in a week\"]\n    if not (is_digits(rate[0:4]) and rate[4] == \".\" and is_digits(rate[5:7])):\n        errs = errs + [\"rate '\" + rate + \"' is not like 0025.50\"]\n    elif value(rate[0:4]) * 100 + value(rate[5:7]) == 0:\n        errs = errs + [\"rate is 0\"]\n    return errs\n\ndef check(lines):\n    out = []\n    ids = []\n    for i in range(0, len(lines)):\n        line = lines[i]\n        if len(line) != WIDTH:\n            out = out + [[\"is \" + str(len(line)) + \" characters, not \" + str(WIDTH) + \" - its columns cannot be trusted\"]]\n        else:\n            errs = field_errors(line)\n            for k in range(0, len(ids)):\n                if ids[k][0] == line[0:6] and len(errs) == 0:\n                    errs = errs + [\"id \" + line[0:6] + \" is already on line \" + str(ids[k][1] + 1)]\n            if len(errs) == 0:\n                ids = ids + [[line[0:6], i]]\n            out = out + [errs]\n    return out\n\ndef first_error(lines):\n    all = check(lines)\n    for i in range(0, len(all)):\n        if len(all[i]) > 0:\n            return [i, all[i][0]]\n    return []\n\ndef pay(line):\n    tenths = value(line[30:33]) * 10 + value(line[34:35])\n    cents = value(line[35:39]) * 100 + value(line[40:42])\n    t = tenths * cents\n    q = 0\n    n = t * 2 + 10\n    while n >= 20:\n        m = 20\n        k = 1\n        while m + m <= n:\n            m = m + m\n            k = k + k\n        n = n - m\n        q = q + k\n    return q\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "0\nx\n4\nabc\n4\n1000aaBad Name 9          Sale999.9abcd.ef\n5\n0\n5\n99\n5\nx\n5\n\n4\n\n2\n6\n",
      "screen": "== Fixed-width report ==\nPayroll lines in fixed columns: checked before they are trusted, every error at once.\n\n1) lines  2) check  3) pay report  4) add a line  5) remove a line  6) quit\nchoice> 0\nPick a number from 1 to 6.\n\n1) lines  2) check  3) pay report  4) add a line  5) remove a line  6) quit\nchoice> x\nPick a number from 1 to 6.\n\n1) lines  2) check  3) pay report  4) add a line  5) remove a line  6) quit\nchoice> 4\nnew line (42 characters, as on the ruler)> abc\nAdded, with 1 error:\n  is 3 characters, not 42 - its columns cannot be trusted\n\n1) lines  2) check  3) pay report  4) add a line  5) remove a line  6) quit\nchoice> 4\nnew line (42 characters, as on the ruler)> 1000aaBad Name 9          Sale999.9abcd.ef\nAdded, with 5 errors:\n  id '1000aa' is not six digits\n  name 'Bad Name 9' must start in column 6 and be letters, spaces, - ' or .\n  dept 'Sale' is not four capital letters\n  hours 999.9 is more than the 168 in a week\n  rate 'abcd.ef' is not like 0025.50\n\n1) lines  2) check  3) pay report  4) add a line  5) remove a line  6) quit\nchoice> 5\nline number to remove> 0\nThere is no line 0.\n\n1) lines  2) check  3) pay report  4) add a line  5) remove a line  6) quit\nchoice> 5\nline number to remove> 99\nThere is no line 99.\n\n1) lines  2) check  3) pay report  4) add a line  5) remove a line  6) quit\nchoice> 5\nline number to remove> x\nThere is no line x.\n\n1) lines  2) check  3) pay report  4) add a line  5) remove a line  6) quit\nchoice> 5\nline number to remove> \nCancelled.\n\n1) lines  2) check  3) pay report  4) add a line  5) remove a line  6) quit\nchoice> 4\nnew line (42 characters, as on the ruler)> \nCancelled.\n\n1) lines  2) check  3) pay report  4) add a line  5) remove a line  6) quit\nchoice> 2\n  line 5: id '10000X' is not six digits\n  line 5: dept 'tech' is not four capital letters\n  line 6: hours 170.0 is more than the 168 in a week\n  line 7: dept 'OPS ' is not four capital letters\n  line 7: rate is 0\n  line 8: id 100002 is already on line 2\n  line 9: is 38 characters, not 42 - its columns cannot be trusted\n  line 10: is 3 characters, not 42 - its columns cannot be trusted\n  line 11: id '1000aa' is not six digits\n  line 11: name 'Bad Name 9' must start in column 6 and be letters, spaces, - ' or .\n  line 11: dept 'Sale' is not four capital letters\n  line 11: hours 999.9 is more than the 168 in a week\n  line 11: rate 'abcd.ef' is not like 0025.50\nChecking everything: 13 errors on 7 of the 11 lines.\nStopping at the first would have said only: line 5: id '10000X' is not six digits - and the next run would find the next one.\n\n1) lines  2) check  3) pay report  4) add a line  5) remove a line  6) quit\nchoice> 6\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "1\n2\n3\n4\n100010Jo Kim              TECH040.00030.00\n3\n5\n9\n2\n6\n",
      "screen": "== Fixed-width report ==\nPayroll lines in fixed columns: checked before they are trusted, every error at once.\n\n1) lines  2) check  3) pay report  4) add a line  5) remove a line  6) quit\nchoice> 1\n              1         2         3         4\n    012345678901234567890123456789012345678901\n 1  100001Ana Lopez           SALE040.00025.50\n 2  100002Ben O'Hara          SALE037.50022.00\n 3  100003Chloe Dubois        TECH042.50031.75\n 4  100004Dev Patel           TECH038.00029.40\n 5  10000XEli Moss            tech040.00024.00\n 6  100006Fay Lin             SALE170.00025.00\n 7  100007Gus Young           OPS 035.00000.00\n 8  100002Hana Ito            TECH036.00027.50\n 9  100009Ivo Novak           SALE040.0002\n    id    name                dept hours rate\n\n1) lines  2) check  3) pay report  4) add a line  5) remove a line  6) quit\nchoice> 2\n  line 5: id '10000X' is not six digits\n  line 5: dept 'tech' is not four capital letters\n  line 6: hours 170.0 is more than the 168 in a week\n  line 7: dept 'OPS ' is not four capital letters\n  line 7: rate is 0\n  line 8: id 100002 is already on line 2\n  line 9: is 38 characters, not 42 - its columns cannot be trusted\nChecking everything: 7 errors on 5 of the 9 lines.\nStopping at the first would have said only: line 5: id '10000X' is not six digits - and the next run would find the next one.\n\n1) lines  2) check  3) pay report  4) add a line  5) remove a line  6) quit\nchoice> 3\n 1  Ana Lopez, SALE: 040.0 h x 0025.50 = 1,020.00\n 2  Ben O'Hara, SALE: 037.5 h x 0022.00 = 825.00\n 3  Chloe Dubois, TECH: 042.5 h x 0031.75 = 1,349.38\n 4  Dev Patel, TECH: 038.0 h x 0029.40 = 1,117.20\n  SALE      1,845.00\n  TECH      2,466.58\n  all       4,311.58  from 4 good lines; 5 left out until they are fixed\n\n1) lines  2) check  3) pay report  4) add a line  5) remove a line  6) quit\nchoice> 4\nnew line (42 characters, as on the ruler)> 100010Jo Kim              TECH040.00030.00\nAdded; it is good.\n\n1) lines  2) check  3) pay report  4) add a line  5) remove a line  6) quit\nchoice> 3\n 1  Ana Lopez, SALE: 040.0 h x 0025.50 = 1,020.00\n 2  Ben O'Hara, SALE: 037.5 h x 0022.00 = 825.00\n 3  Chloe Dubois, TECH: 042.5 h x 0031.75 = 1,349.38\n 4  Dev Patel, TECH: 038.0 h x 0029.40 = 1,117.20\n10  Jo Kim, TECH: 040.0 h x 0030.00 = 1,200.00\n  SALE      1,845.00\n  TECH      3,666.58\n  all       5,511.58  from 5 good lines; 5 left out until they are fixed\n\n1) lines  2) check  3) pay report  4) add a line  5) remove a line  6) quit\nchoice> 5\nline number to remove> 9\nRemoved line 9.\n\n1) lines  2) check  3) pay report  4) add a line  5) remove a line  6) quit\nchoice> 2\n  line 5: id '10000X' is not six digits\n  line 5: dept 'tech' is not four capital letters\n  line 6: hours 170.0 is more than the 168 in a week\n  line 7: dept 'OPS ' is not four capital letters\n  line 7: rate is 0\n  line 8: id 100002 is already on line 2\nChecking everything: 6 errors on 4 of the 9 lines.\nStopping at the first would have said only: line 5: id '10000X' is not six digits - and the next run would find the next one.\n\n1) lines  2) check  3) pay report  4) add a line  5) remove a line  6) quit\nchoice> 6\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "fixed-width-record-parser",
      "caseId": "203-fixed-width-record-parser",
      "title": "Fixed-width records: where clamping does the most damage"
    },
    {
      "slug": "schema-validator-all-errors",
      "caseId": "209-schema-validator-all-errors",
      "title": "Report every error, not the first one"
    }
  ],
  "updated": "2026-10-15"
}
