{
  "id": "P062",
  "slug": "expression-calculator",
  "key": "P062-expression-calculator",
  "title": "Expression calculator",
  "summary": "Expressions typed the usual way - + - * /, whole powers with ^, brackets and minus signs - turned into postfix by Dijkstra's shunting-yard algorithm and worked out on a stack, both shown step by step, in exact fractions; postfix can be typed directly too.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P062 - Expression calculator\n\nType an expression the usual way, with + - * /, ^ for whole powers,\nbrackets and minus signs, like (1 + 2) * -3 / 4. The calculator turns it\ninto postfix order with Dijkstra's shunting-yard algorithm, works the\npostfix out on a stack, and can show both step by step. Every value is an\nexact fraction. Postfix can also be typed in directly.\n\n- `main.eml` - the menu, reading expressions and postfix, the step tables\n  and the answer\n- `expr.eml` - tokens, the shunting yard, and the stack that works postfix\n  out\n- `frac.eml` - exact fractions, from P007 by way of P038, P044, P056 and\n  P060\n\nHow each part works:\n\n- Tokens: numbers (whole or decimal), the operators, and brackets. A minus\n  sign at the start, after an operator or after \"(\" is a sign, shown as\n  neg; anywhere else it subtracts.\n- Shunting yard. A number goes straight to the output. An operator first\n  moves to the output every operator waiting on the stack that binds\n  tighter, or as tight when it groups to the left, and then waits itself.\n  \"(\" waits, and \")\" moves operators out until its \"(\".\n- Precedence, from loosest: + and -, then * and /, then the sign, then ^.\n  So -2^2 is -(2^2) = -4, and (-2)^2 is 4. ^ groups to the right, so\n  2 ^ 3 ^ 2 is 2^9 = 512; the rest group to the left, so 8 - 3 - 2 is 3.\n- Stack. As in the corpus case `rpn-evaluator`, a number is pushed, and an\n  operator pops what it needs - two numbers, or one for the sign - and\n  pushes its result. One number left at the end is the answer.\n- The answer is a fraction in lowest terms, with its decimal. A decimal\n  that does not end is rounded to six places and marked with ~.\n- A power must be a whole number no larger than 20. Dividing by zero and\n  zero to a negative power are refused with the reason.\n\nWhat is checked: menu choices 1 to 4; expressions of up to 80 characters\nwith numbers, + - * / ^, brackets and spaces; brackets that match;\noperators with numbers to work on; postfix tokens separated by spaces.\nAn empty answer cancels.\n\nSessions: `sessions/basic.in` gives:\n- (1 + 2) * -3 / 4 step by step, = -9/4 = -2.25;\n- 2 ^ 3 ^ 2 step by step, grouping to the right, = 512;\n- 1/3 + 1/6 = 1/2, -2^2 + (-2)^2 = 0, 10 / 4 - 0.5 = 2, and 1/3, which is\n  ~0.333333;\n- the postfix 3 4 + 2 *, = 14.\n\n`sessions/bad-input.in` gives:\n- menu choices 0 and x, and an empty expression;\n- unbalanced brackets both ways, a $, the number 1.2.3, \"2 +\" and \"2 3\";\n- division by zero, 2 ^ 0.5, 2 ^ 21 and 0 ^ -1;\n- an empty pair of brackets, and 81 characters;\n- the postfix \"3 +\", \"1 x\" and an empty one.\n\nBuilt on the verified corpus case `rpn-evaluator` (postfix worked out by\npushing numbers and popping two for each operator).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P062 expression calculator: type an expression the usual way - with + - *\n# /, ^ for whole powers, brackets and minus signs - and see it turned into\n# postfix order by the shunting-yard algorithm and worked out on a stack,\n# step by step, in exact fractions. Postfix can be typed directly too.\nimport expr\nimport frac\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 words(s):\n    [] => out\n    \"\" => word\n    for c in s + \" \":\n        if c == \" \":\n            if word != \"\":\n                out + [word] => out\n            \"\" => word\n        else:\n            word + c => word\n    return out\n\ndef joined(ts):\n    \"\" => s\n    for t in ts:\n        if s != \"\":\n            s + \" \" => s\n        s + expr.shown(t) => s\n    return s\n\ndef numbers(xs):\n    \"\" => s\n    for x in xs:\n        if s != \"\":\n            s + \" \" => s\n        s + frac.text(x) => s\n    return s\n\ndef padded(s, width):\n    while len(s) < width:\n        s + \" \" => s\n    return s\n\ndef answer(x):\n    # 7/2 = 3.5; a whole number as it is; a long decimal rounded, marked ~.\n    if x[1] == 1:\n        return frac.text(x)\n    frac.decimal(x, 6) => d\n    if len(d) > 6 and d[0:6] == \"about \":\n        \"~\" + d[6:len(d)] => d\n    return frac.text(x) + \" = \" + d\n\ndef show_steps(postfix_steps, eval_steps):\n    if len(postfix_steps) > 0:\n        \"Shunting yard - each token, then the output so far and the operators waiting:\" ^0\n        for s in postfix_steps:\n            \"  \" + padded(expr.shown(s[0]), 6) + padded(joined(s[1]), 28) + \" |\" => line\n            if len(s[2]) > 0:\n                line + \" \" + joined(s[2]) => line\n            line ^0\n    \"Stack - each postfix token, then the stack:\" ^0\n    for s in eval_steps:\n        (\"  \" + padded(expr.shown(s[0]), 6) + numbers(s[1])) ^0\n\ndef work_out(steps):\n    trim(input(\"expression> \")) => s\n    if s == \"\":\n        \"Cancelled.\" ^0\n        return\n    if len(s) > 80:\n        \"Keep an expression to 80 characters.\" ^0\n        return\n    expr.tokens(s) => ts\n    if len(ts) > 0 and ts[0][0] == \"bad\":\n        (\"Cannot read \" + ts[0][1] + \".\") ^0\n        return\n    expr.to_postfix(ts) => p\n    if p[2] != \"\":\n        (\"Not an expression: \" + p[2] + \".\") ^0\n        return\n    if len(p[0]) == 0:\n        \"There is nothing to work out.\" ^0\n        return\n    run(p[0], p[1], steps)\n\ndef run(postfix, postfix_steps, steps):\n    expr.evaluate(postfix) => r\n    if steps:\n        (\"Postfix: \" + joined(postfix)) ^0\n        show_steps(postfix_steps, r[1])\n    if r[2] != \"\":\n        (\"Cannot be worked out: \" + r[2] + \".\") ^0\n        return\n    (\"= \" + answer(r[0])) ^0\n\ndef postfix_input():\n    words(trim(input(\"postfix, tokens with spaces between (neg for a minus sign)> \"))) => ws\n    if len(ws) == 0:\n        \"Cancelled.\" ^0\n        return\n    [] => ts\n    for w in ws:\n        if w == \"+\" or w == \"-\" or w == \"*\" or w == \"/\" or w == \"^\" or w == \"neg\":\n            ts + [[\"op\", w]] => ts\n        else:\n            frac.parse(w) => x\n            if len(x) == 0:\n                (\"Not a number or an operator: \" + w + \".\") ^0\n                return\n            ts + [[\"num\", x]] => ts\n    run(ts, [], True)\n\n\"== Expression calculator ==\" ^0\n\"Exact fractions: + - * /, ^ for whole powers, brackets and minus signs, like (1 + 2) * -3 / 4.\" ^0\nTrue => running\nwhile running:\n    \"\" ^0\n    \"1) step by step  2) just the answer  3) type postfix  4) quit\" ^0\n    trim(input(\"choice> \")) => choice\n    if choice == \"1\":\n        work_out(True)\n    elif choice == \"2\":\n        work_out(False)\n    elif choice == \"3\":\n        postfix_input()\n    elif choice == \"4\":\n        False => running\n    else:\n        \"Pick a number from 1 to 4.\" ^0\n\"Bye.\" ^0\n",
      "python": "import expr\nimport frac\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 words(s):\n    out = []\n    word = \"\"\n    for c in s + \" \":\n        if c == \" \":\n            if word != \"\":\n                out = out + [word]\n            word = \"\"\n        else:\n            word = word + c\n    return out\n\ndef joined(ts):\n    s = \"\"\n    for t in ts:\n        if s != \"\":\n            s = s + \" \"\n        s = s + expr.shown(t)\n    return s\n\ndef numbers(xs):\n    s = \"\"\n    for x in xs:\n        if s != \"\":\n            s = s + \" \"\n        s = s + frac.text(x)\n    return s\n\ndef padded(s, width):\n    while len(s) < width:\n        s = s + \" \"\n    return s\n\ndef answer(x):\n    if x[1] == 1:\n        return frac.text(x)\n    d = frac.decimal(x, 6)\n    if len(d) > 6 and d[0:6] == \"about \":\n        d = \"~\" + d[6:len(d)]\n    return frac.text(x) + \" = \" + d\n\ndef show_steps(postfix_steps, eval_steps):\n    if len(postfix_steps) > 0:\n        print(\"Shunting yard - each token, then the output so far and the operators waiting:\")\n        for s in postfix_steps:\n            line = \"  \" + padded(expr.shown(s[0]), 6) + padded(joined(s[1]), 28) + \" |\"\n            if len(s[2]) > 0:\n                line = line + \" \" + joined(s[2])\n            print(line)\n    print(\"Stack - each postfix token, then the stack:\")\n    for s in eval_steps:\n        print(\"  \" + padded(expr.shown(s[0]), 6) + numbers(s[1]))\n\ndef work_out(steps):\n    s = trim(input(\"expression> \"))\n    if s == \"\":\n        print(\"Cancelled.\")\n        return\n    if len(s) > 80:\n        print(\"Keep an expression to 80 characters.\")\n        return\n    ts = expr.tokens(s)\n    if len(ts) > 0 and ts[0][0] == \"bad\":\n        print(\"Cannot read \" + ts[0][1] + \".\")\n        return\n    p = expr.to_postfix(ts)\n    if p[2] != \"\":\n        print(\"Not an expression: \" + p[2] + \".\")\n        return\n    if len(p[0]) == 0:\n        print(\"There is nothing to work out.\")\n        return\n    run(p[0], p[1], steps)\n\ndef run(postfix, postfix_steps, steps):\n    r = expr.evaluate(postfix)\n    if steps:\n        print(\"Postfix: \" + joined(postfix))\n        show_steps(postfix_steps, r[1])\n    if r[2] != \"\":\n        print(\"Cannot be worked out: \" + r[2] + \".\")\n        return\n    print(\"= \" + answer(r[0]))\n\ndef postfix_input():\n    ws = words(trim(input(\"postfix, tokens with spaces between (neg for a minus sign)> \")))\n    if len(ws) == 0:\n        print(\"Cancelled.\")\n        return\n    ts = []\n    for w in ws:\n        if w == \"+\" or w == \"-\" or w == \"*\" or w == \"/\" or w == \"^\" or w == \"neg\":\n            ts = ts + [[\"op\", w]]\n        else:\n            x = frac.parse(w)\n            if len(x) == 0:\n                print(\"Not a number or an operator: \" + w + \".\")\n                return\n            ts = ts + [[\"num\", x]]\n    run(ts, [], True)\n\nprint(\"== Expression calculator ==\")\nprint(\"Exact fractions: + - * /, ^ for whole powers, brackets and minus signs, like (1 + 2) * -3 / 4.\")\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"1) step by step  2) just the answer  3) type postfix  4) quit\")\n    choice = trim(input(\"choice> \"))\n    if choice == \"1\":\n        work_out(True)\n    elif choice == \"2\":\n        work_out(False)\n    elif choice == \"3\":\n        postfix_input()\n    elif choice == \"4\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 4.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "expr.eml",
      "eml": "# P062 expression calculator - tokens, the shunting-yard conversion from the\n# usual (infix) order to postfix, and the stack that works postfix out.\n# A token is [\"num\", fraction], [\"op\", \"+\"], [\"(\", \"\"] or [\")\", \"\"]. The\n# operators: + - * / and ^ (a whole-number power), and \"neg\", a minus sign\n# in front of a number or a bracket.\nimport frac\n\ndef precedence(op):\n    if op == \"+\" or op == \"-\":\n        return 1\n    if op == \"*\" or op == \"/\":\n        return 2\n    if op == \"neg\":\n        return 3\n    return 4\n\ndef right_assoc(op):\n    # 2 ^ 3 ^ 2 is 2 ^ (3 ^ 2), and - - 3 is -(-3); the rest group left.\n    return op == \"^\" or op == \"neg\"\n\ndef tokens(s):\n    # The tokens of s, or [[\"bad\", why]].\n    [] => out\n    0 => i\n    while i < len(s):\n        s[i] => c\n        if c == \" \":\n            i + 1 => i\n        elif (c >= \"0\" and c <= \"9\") or c == \".\":\n            i => j\n            while j < len(s) and ((s[j] >= \"0\" and s[j] <= \"9\") or s[j] == \".\"):\n                j + 1 => j\n            frac.parse(s[i:j]) => x\n            if len(x) == 0:\n                return [[\"bad\", \"the number \" + s[i:j]]]\n            out + [[\"num\", x]] => out\n            j => i\n        elif c == \"+\" or c == \"-\" or c == \"*\" or c == \"/\" or c == \"^\":\n            # A minus with nothing, an operator or \"(\" before it is a sign.\n            if c == \"-\" and (len(out) == 0 or out[len(out) - 1][0] == \"op\" or out[len(out) - 1][0] == \"(\"):\n                out + [[\"op\", \"neg\"]] => out\n            else:\n                out + [[\"op\", c]] => out\n            i + 1 => i\n        elif c == \"(\" or c == \")\":\n            out + [[c, \"\"]] => out\n            i + 1 => i\n        else:\n            return [[\"bad\", \"the character \" + c]]\n    return out\n\ndef shown(t):\n    if t[0] == \"num\":\n        return frac.text(t[1])\n    if t[0] == \"op\":\n        if t[1] == \"neg\":\n            return \"neg\"\n        return t[1]\n    return t[0]\n\ndef to_postfix(ts):\n    # Dijkstra's shunting yard. Numbers go straight to the output; an\n    # operator first moves to the output every operator on the stack that\n    # binds tighter (or as tight, when it groups left), then waits on the\n    # stack; \"(\" waits, and \")\" moves operators out until its \"(\". Returns\n    # [postfix tokens, steps, problem]: each step [token, output, stack].\n    [] => output\n    [] => stack\n    [] => steps\n    \"\" => problem\n    for t in ts:\n        if t[0] == \"num\":\n            output + [t] => output\n        elif t[0] == \"op\":\n            True => moving\n            while moving and len(stack) > 0 and stack[len(stack) - 1][0] == \"op\" and t[1] != \"neg\":\n                stack[len(stack) - 1][1] => top\n                if precedence(top) > precedence(t[1]) or (precedence(top) == precedence(t[1]) and not right_assoc(t[1])):\n                    output + [stack[len(stack) - 1]] => output\n                    stack[0:len(stack) - 1] => stack\n                else:\n                    False => moving\n            stack + [t] => stack\n        elif t[0] == \"(\":\n            stack + [t] => stack\n        else:\n            while len(stack) > 0 and stack[len(stack) - 1][0] != \"(\":\n                output + [stack[len(stack) - 1]] => output\n                stack[0:len(stack) - 1] => stack\n            if len(stack) == 0:\n                \"a ) with no ( before it\" => problem\n                return [output, steps, problem]\n            stack[0:len(stack) - 1] => stack\n        steps + [[t, output, stack]] => steps\n    while len(stack) > 0:\n        if stack[len(stack) - 1][0] == \"(\":\n            \"a ( that is never closed\" => problem\n            return [output, steps, problem]\n        output + [stack[len(stack) - 1]] => output\n        stack[0:len(stack) - 1] => stack\n    return [output, steps, problem]\n\ndef power(x, n):\n    # x to a whole power n, exactly; the caller has checked x != 0 when n < 0.\n    [1, 1] => r\n    abs(n) => k\n    for i in [1:k]:\n        frac.times(r, x) => r\n    if n < 0:\n        return frac.over([1, 1], r)\n    return r\n\ndef apply(op, a, b):\n    # [value, problem] for a op b.\n    if op == \"+\":\n        return [frac.plus(a, b), \"\"]\n    if op == \"-\":\n        return [frac.minus(a, b), \"\"]\n    if op == \"*\":\n        return [frac.times(a, b), \"\"]\n    if op == \"/\":\n        if frac.is_zero(b):\n            return [[0, 1], \"division by zero\"]\n        return [frac.over(a, b), \"\"]\n    if b[1] != 1:\n        return [[0, 1], \"a power that is not a whole number: \" + frac.text(b)]\n    if abs(b[0]) > 20:\n        return [[0, 1], \"a power above 20\"]\n    if frac.is_zero(a) and b[0] < 0:\n        return [[0, 1], \"zero to a negative power\"]\n    return [power(a, b[0]), \"\"]\n\ndef evaluate(postfix):\n    # The corpus case rpn-evaluator's stack: push numbers; an operator pops\n    # what it needs - two, or one for neg - and pushes its result. Returns\n    # [value, steps, problem]: each step [token, stack after].\n    [] => stack\n    [] => steps\n    for t in postfix:\n        if t[0] == \"num\":\n            stack + [t[1]] => stack\n        elif t[1] == \"neg\":\n            if len(stack) < 1:\n                return [[0, 1], steps, \"a minus sign with no number after it\"]\n            frac.minus([0, 1], stack[len(stack) - 1]) => stack[len(stack) - 1]\n        else:\n            if len(stack) < 2:\n                return [[0, 1], steps, t[1] + \" without two numbers to work on\"]\n            stack[len(stack) - 2] => a\n            stack[len(stack) - 1] => b\n            stack[0:len(stack) - 2] => stack\n            apply(t[1], a, b) => res\n            if res[1] != \"\":\n                return [[0, 1], steps, res[1]]\n            stack + [res[0]] => stack\n        steps + [[t, stack[0:len(stack)]]] => steps\n    if len(stack) != 1:\n        return [[0, 1], steps, \"numbers with no operator between them\"]\n    return [stack[0], steps, \"\"]\n",
      "python": "import frac\n\ndef precedence(op):\n    if op == \"+\" or op == \"-\":\n        return 1\n    if op == \"*\" or op == \"/\":\n        return 2\n    if op == \"neg\":\n        return 3\n    return 4\n\ndef right_assoc(op):\n    return op == \"^\" or op == \"neg\"\n\ndef tokens(s):\n    out = []\n    i = 0\n    while i < len(s):\n        c = s[i]\n        if c == \" \":\n            i = i + 1\n        elif c >= \"0\" and c <= \"9\" or c == \".\":\n            j = i\n            while j < len(s) and (s[j] >= \"0\" and s[j] <= \"9\" or s[j] == \".\"):\n                j = j + 1\n            x = frac.parse(s[i:j])\n            if len(x) == 0:\n                return [[\"bad\", \"the number \" + s[i:j]]]\n            out = out + [[\"num\", x]]\n            i = j\n        elif c == \"+\" or c == \"-\" or c == \"*\" or c == \"/\" or c == \"^\":\n            if c == \"-\" and (len(out) == 0 or out[len(out) - 1][0] == \"op\" or out[len(out) - 1][0] == \"(\"):\n                out = out + [[\"op\", \"neg\"]]\n            else:\n                out = out + [[\"op\", c]]\n            i = i + 1\n        elif c == \"(\" or c == \")\":\n            out = out + [[c, \"\"]]\n            i = i + 1\n        else:\n            return [[\"bad\", \"the character \" + c]]\n    return out\n\ndef shown(t):\n    if t[0] == \"num\":\n        return frac.text(t[1])\n    if t[0] == \"op\":\n        if t[1] == \"neg\":\n            return \"neg\"\n        return t[1]\n    return t[0]\n\ndef to_postfix(ts):\n    output = []\n    stack = []\n    steps = []\n    problem = \"\"\n    for t in ts:\n        if t[0] == \"num\":\n            output = output + [t]\n        elif t[0] == \"op\":\n            moving = True\n            while moving and len(stack) > 0 and stack[len(stack) - 1][0] == \"op\" and t[1] != \"neg\":\n                top = stack[len(stack) - 1][1]\n                if precedence(top) > precedence(t[1]) or precedence(top) == precedence(t[1]) and not right_assoc(t[1]):\n                    output = output + [stack[len(stack) - 1]]\n                    stack = stack[0:len(stack) - 1]\n                else:\n                    moving = False\n            stack = stack + [t]\n        elif t[0] == \"(\":\n            stack = stack + [t]\n        else:\n            while len(stack) > 0 and stack[len(stack) - 1][0] != \"(\":\n                output = output + [stack[len(stack) - 1]]\n                stack = stack[0:len(stack) - 1]\n            if len(stack) == 0:\n                problem = \"a ) with no ( before it\"\n                return [output, steps, problem]\n            stack = stack[0:len(stack) - 1]\n        steps = steps + [[t, output, stack]]\n    while len(stack) > 0:\n        if stack[len(stack) - 1][0] == \"(\":\n            problem = \"a ( that is never closed\"\n            return [output, steps, problem]\n        output = output + [stack[len(stack) - 1]]\n        stack = stack[0:len(stack) - 1]\n    return [output, steps, problem]\n\ndef power(x, n):\n    r = [1, 1]\n    k = abs(n)\n    for i in range(1, k+1):\n        r = frac.times(r, x)\n    if n < 0:\n        return frac.over([1, 1], r)\n    return r\n\ndef apply(op, a, b):\n    if op == \"+\":\n        return [frac.plus(a, b), \"\"]\n    if op == \"-\":\n        return [frac.minus(a, b), \"\"]\n    if op == \"*\":\n        return [frac.times(a, b), \"\"]\n    if op == \"/\":\n        if frac.is_zero(b):\n            return [[0, 1], \"division by zero\"]\n        return [frac.over(a, b), \"\"]\n    if b[1] != 1:\n        return [[0, 1], \"a power that is not a whole number: \" + frac.text(b)]\n    if abs(b[0]) > 20:\n        return [[0, 1], \"a power above 20\"]\n    if frac.is_zero(a) and b[0] < 0:\n        return [[0, 1], \"zero to a negative power\"]\n    return [power(a, b[0]), \"\"]\n\ndef evaluate(postfix):\n    stack = []\n    steps = []\n    for t in postfix:\n        if t[0] == \"num\":\n            stack = stack + [t[1]]\n        elif t[1] == \"neg\":\n            if len(stack) < 1:\n                return [[0, 1], steps, \"a minus sign with no number after it\"]\n            stack[len(stack) - 1] = frac.minus([0, 1], stack[len(stack) - 1])\n        else:\n            if len(stack) < 2:\n                return [[0, 1], steps, t[1] + \" without two numbers to work on\"]\n            a = stack[len(stack) - 2]\n            b = stack[len(stack) - 1]\n            stack = stack[0:len(stack) - 2]\n            res = apply(t[1], a, b)\n            if res[1] != \"\":\n                return [[0, 1], steps, res[1]]\n            stack = stack + [res[0]]\n        steps = steps + [[t, stack[0:len(stack)]]]\n    if len(stack) != 1:\n        return [[0, 1], steps, \"numbers with no operator between them\"]\n    return [stack[0], steps, \"\"]\n"
    },
    {
      "name": "frac.eml",
      "eml": "# P062 expression calculator - exact fractions, from P007 (calculator) by\n# way of P038, P044, P056 and P060.\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\n\n2\n(1 + 2\n2\n1 + 2)\n2\n2 $ 3\n2\n1.2.3\n2\n2 +\n2\n2 3\n2\n1 / (2 - 2)\n2\n2 ^ 0.5\n2\n2 ^ 21\n2\n0 ^ -1\n2\n()\n2\n1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1\n3\n3 +\n3\n1 x\n3\n\n4\n",
      "screen": "== Expression calculator ==\nExact fractions: + - * /, ^ for whole powers, brackets and minus signs, like (1 + 2) * -3 / 4.\n\n1) step by step  2) just the answer  3) type postfix  4) quit\nchoice> 0\nPick a number from 1 to 4.\n\n1) step by step  2) just the answer  3) type postfix  4) quit\nchoice> x\nPick a number from 1 to 4.\n\n1) step by step  2) just the answer  3) type postfix  4) quit\nchoice> 1\nexpression> \nCancelled.\n\n1) step by step  2) just the answer  3) type postfix  4) quit\nchoice> 2\nexpression> (1 + 2\nNot an expression: a ( that is never closed.\n\n1) step by step  2) just the answer  3) type postfix  4) quit\nchoice> 2\nexpression> 1 + 2)\nNot an expression: a ) with no ( before it.\n\n1) step by step  2) just the answer  3) type postfix  4) quit\nchoice> 2\nexpression> 2 $ 3\nCannot read the character $.\n\n1) step by step  2) just the answer  3) type postfix  4) quit\nchoice> 2\nexpression> 1.2.3\nCannot read the number 1.2.3.\n\n1) step by step  2) just the answer  3) type postfix  4) quit\nchoice> 2\nexpression> 2 +\nCannot be worked out: + without two numbers to work on.\n\n1) step by step  2) just the answer  3) type postfix  4) quit\nchoice> 2\nexpression> 2 3\nCannot be worked out: numbers with no operator between them.\n\n1) step by step  2) just the answer  3) type postfix  4) quit\nchoice> 2\nexpression> 1 / (2 - 2)\nCannot be worked out: division by zero.\n\n1) step by step  2) just the answer  3) type postfix  4) quit\nchoice> 2\nexpression> 2 ^ 0.5\nCannot be worked out: a power that is not a whole number: 1/2.\n\n1) step by step  2) just the answer  3) type postfix  4) quit\nchoice> 2\nexpression> 2 ^ 21\nCannot be worked out: a power above 20.\n\n1) step by step  2) just the answer  3) type postfix  4) quit\nchoice> 2\nexpression> 0 ^ -1\nCannot be worked out: zero to a negative power.\n\n1) step by step  2) just the answer  3) type postfix  4) quit\nchoice> 2\nexpression> ()\nThere is nothing to work out.\n\n1) step by step  2) just the answer  3) type postfix  4) quit\nchoice> 2\nexpression> 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1\nKeep an expression to 80 characters.\n\n1) step by step  2) just the answer  3) type postfix  4) quit\nchoice> 3\npostfix, tokens with spaces between (neg for a minus sign)> 3 +\nPostfix: 3 +\nStack - each postfix token, then the stack:\n  3     3\nCannot be worked out: + without two numbers to work on.\n\n1) step by step  2) just the answer  3) type postfix  4) quit\nchoice> 3\npostfix, tokens with spaces between (neg for a minus sign)> 1 x\nNot a number or an operator: x.\n\n1) step by step  2) just the answer  3) type postfix  4) quit\nchoice> 3\npostfix, tokens with spaces between (neg for a minus sign)> \nCancelled.\n\n1) step by step  2) just the answer  3) type postfix  4) quit\nchoice> 4\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "1\n(1 + 2) * -3 / 4\n1\n2 ^ 3 ^ 2\n2\n1/3 + 1/6\n2\n-2^2 + (-2)^2\n2\n10 / 4 - 0.5\n2\n1/3\n3\n3 4 + 2 *\n4\n",
      "screen": "== Expression calculator ==\nExact fractions: + - * /, ^ for whole powers, brackets and minus signs, like (1 + 2) * -3 / 4.\n\n1) step by step  2) just the answer  3) type postfix  4) quit\nchoice> 1\nexpression> (1 + 2) * -3 / 4\nPostfix: 1 2 + 3 neg * 4 /\nShunting yard - each token, then the output so far and the operators waiting:\n  (                                  | (\n  1     1                            | (\n  +     1                            | ( +\n  2     1 2                          | ( +\n  )     1 2 +                        |\n  *     1 2 +                        | *\n  neg   1 2 +                        | * neg\n  3     1 2 + 3                      | * neg\n  /     1 2 + 3 neg *                | /\n  4     1 2 + 3 neg * 4              | /\nStack - each postfix token, then the stack:\n  1     1\n  2     1 2\n  +     3\n  3     3 3\n  neg   3 -3\n  *     -9\n  4     -9 4\n  /     -9/4\n= -9/4 = -2.25\n\n1) step by step  2) just the answer  3) type postfix  4) quit\nchoice> 1\nexpression> 2 ^ 3 ^ 2\nPostfix: 2 3 2 ^ ^\nShunting yard - each token, then the output so far and the operators waiting:\n  2     2                            |\n  ^     2                            | ^\n  3     2 3                          | ^\n  ^     2 3                          | ^ ^\n  2     2 3 2                        | ^ ^\nStack - each postfix token, then the stack:\n  2     2\n  3     2 3\n  2     2 3 2\n  ^     2 9\n  ^     512\n= 512\n\n1) step by step  2) just the answer  3) type postfix  4) quit\nchoice> 2\nexpression> 1/3 + 1/6\n= 1/2 = 0.5\n\n1) step by step  2) just the answer  3) type postfix  4) quit\nchoice> 2\nexpression> -2^2 + (-2)^2\n= 0\n\n1) step by step  2) just the answer  3) type postfix  4) quit\nchoice> 2\nexpression> 10 / 4 - 0.5\n= 2\n\n1) step by step  2) just the answer  3) type postfix  4) quit\nchoice> 2\nexpression> 1/3\n= 1/3 = ~0.333333\n\n1) step by step  2) just the answer  3) type postfix  4) quit\nchoice> 3\npostfix, tokens with spaces between (neg for a minus sign)> 3 4 + 2 *\nPostfix: 3 4 + 2 *\nStack - each postfix token, then the stack:\n  3     3\n  4     3 4\n  +     7\n  2     7 2\n  *     14\n= 14\n\n1) step by step  2) just the answer  3) type postfix  4) quit\nchoice> 4\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "rpn-evaluator",
      "caseId": "093-rpn-evaluator",
      "title": "Reverse Polish Notation evaluator"
    }
  ],
  "updated": "2026-10-14"
}
