# tags: rpn, calc, numbers, awk, code # # awk reverse polish notation parser # Michael Sanders 2023 # https://busybox.neocities.org/notes/rpn.txt # # operands... # # large numbers? this really depends on your awk # decimal fractions? yes, via a hopfully not too # clever regex that expects decimal points to be # surrounded by digits eg... # # valid 0.5, invalid .5 # valid 5.0, invalid 5. # # operators... # # + addition # - subtraction # * multiplication # / division # % modulus # ^ exponentiation (see footnotes) # # *always* surround input with 'quotes' as some RPN # operators can be misconstrued as meta-characters # by your shell, example... # # echo '0.089 2.5 + 2 * 3 ^' | awk -f rpn.txt # # arbitrary precision using printf format specifier... # # %0.0f 1 # %0.2f 1.00 (default) # %0.9f 1.000000000 # # further reading... # # https://en.wikipedia.org/wiki/Reverse_Polish_notation { RPN($0) } function RPN(input, x, y, z, stack) { split(trim(input), stack, /[[:space:]]+/) z = 0 for (x = 1; x in stack; ++x) { y = stack[x] if (y ~ /^-?[0-9]+(\.[0-9]+)?$/) stack[++z] = y else { if (z < 2) return errormessage(1) if (y == "+") stack[z-1] += stack[z] else if (y == "-") stack[z-1] -= stack[z] else if (y == "*") stack[z-1] *= stack[z] else if (y == "^") stack[z-1] ^= stack[z] # see footnotes else if (y == "/") { if (stack[z] == 0) return errormessage(2); else stack[z-1] /= stack[z] } else if (y == "%") { if (stack[z] == 0) return errormessage(3); else stack[z-1] %= stack[z] } else return errormessage(4, y) --z } } if (z != 1) return errormessage(5); else printf "%0.2f\n", stack[z] } function trim(str) { sub(/^[[:space:]]+/, "", str) # strip leading sub(/[[:space:]]+$/, "", str) # strip trailing return str } function errormessage(m, n, e) { if (m == 1) e = "insufficient operands" if (m == 2) e = "division by zero" if (m == 3) e = "modulo by zero" if (m == 4) e = "invalid operator" if (m == 5) e = "invalid RPN expression" if (n) e = e " " n printf "error: %s\n", e return 1 } # footnotes: exponentiation operators for differing awks... # # awk 'BEGIN {print 2 ^ 3}' should print 8 if using ^ # stack[z-1] ^= stack[z] works on some awks using ^ # stack[z-1] = stack[z-1] ^ stack[z] always works if using ^ # # awk 'BEGIN {print 2 ** 3}' should print 8 if using ** # stack[z-1] **= stack[z] works on some awks using ** # stack[z-1] = stack[z-1] ** stack[z] always works if using ** # eof