Skip to content

Instantly share code, notes, and snippets.

@Quaese
Created October 23, 2022 14:17
Show Gist options
  • Save Quaese/899268eaa69dc159347f83aefd21de1d to your computer and use it in GitHub Desktop.
Save Quaese/899268eaa69dc159347f83aefd21de1d to your computer and use it in GitHub Desktop.
Einfacher Taschenrechner mit umgekehrt ponischer Notation (UPN) / Simple calculator with reverse Polish notation (RPN)

Einfacher Taschenrechner mit umgekehrt ponischer Notation (UPN) / Simple calculator with reverse Polish notation (RPN)

Quellen

HTML

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title></title>
    <link rel="stylesheet" href="">
    <style>
        .calc-wrapper,
        .calc-wrapper * {
            margin: 0;
            padding: 0;
        }

        .calc-wrapper {
            width: 150px;
            padding: 10px;
            border: 1px solid #ccc;
            border-radius: 10px;
            background: #eee;
        }

        .calc-display div {
            font-family: Helvetica, Arial, sans-serif;
            font-size: 14px;
            text-align: right;
            padding: 3px 5px;
            margin: 5px 0 10px;
            border: 1px solid #ddd;
            border-radius: 8px;
            background: #fff;
        }

        .calc-wrapper table {
            width: 150px;
        }

        .calc-wrapper table {
            width: 150px;
        }

        .calc-wrapper table td {
            padding: 2px 1px;
        }

        .calc-wrapper table button {
            width: 100%;
            padding: 3px 0;
        }
    </style>

    <script>
        (function () {
            let operatorButtons,
                digitButtons,

                numberIndex = 0,
                expr = "", //"( 2 + 3 ) * 2",

                display,

                /*
                 * Update display
                 */
                updateDisplay = function (output) {
                    display.innerHTML = output;
                },

                /*
                 * Get priority of an operation
                 */
                operatorPriority = function (operator) {
                    switch (operator) {
                        case '+':
                        case '-':
                            return 1;
                        case '*':
                        case '/':
                            return 2;
                        case '^':
                            return 3;
                    }
                },

                /*
                 * Convert an expression (e.g. 9 - 3 * 2) in UPN (umgekehrt polnische Notation).
                 *
                 * @params
                 * - tokens - (Array) contains numbers, operators, functions in expression-order (e.g. [9, "-", 3, "*", 2])
                 *
                 * @return
                 * upn_output - (Array) representation of the input expression as UPN array (e.g. [9, 3, 2, "*", "-"])
                 */
                exprToUPN = function (tokens) {
                    let upn_output = [],
                        stack = [];

                    tokens.forEach(function (token, index) {
                        // if token is a number
                        if (!isNaN(token)) upn_output.push(token);

                        // if token is a function
                        else if (/sin|cos|ln/.test(token)) stack.push(token);

                        // if token is an operator
                        else if (/^\+|\-|\*|\/$/.test(token)) {
                            while (
                                stack.length > 0 &&
                                /^\+|\-|\*|\/$/.test(stack[stack.length - 1]) &&
                                operatorPriority(token) <= operatorPriority(stack[stack.length - 1])
                            ) {
                                upn_output.push(stack.pop());
                            }

                            // token to stack
                            stack.push(token);
                        }

                        // token is opening bracket
                        else if (/^\($/.test(token)) stack.push(token);

                        // token is closing bracket
                        else if (/^\)$/.test(token)) {
                            while (!/^\($/.test(stack[stack.length - 1])) {
                                if (stack.length === 0) {
                                    updateDisplay('err');
                                    return [];
                                }

                                upn_output.push(stack.pop());
                            }

                            // remove opening bracket from stack
                            stack.pop();

                            // if token is a function
                            if (/sin|cos|ln/.test(stack[stack.length - 1])) upn_output.push(stack[stack.length - 1]);
                        }
                    });

                    while (stack.length > 0) upn_output.push(stack.pop());

                    console.log("exprToUPN: ", stack, upn_output);
                    return upn_output;
                },

                /*
                 * Calculates an expression
                 */
                calculate = function (leftOp, rightOp, op) {
                    switch (op) {
                        case "+":
                            return leftOp + rightOp;
                        case "-":
                            return leftOp - rightOp;
                        case "*":
                            return leftOp * rightOp;
                        case "/":
                            return leftOp / rightOp;
                    }
                },

                /*
                 * Computes the current expression and display the result
                 */
                compute = function () {
                    // extract tokens from the current expression
                    const tokens = expr.trim().replace(/  /g, " ").split(" ").map(function (token) {
                        return isNaN(token) ? token : Number(token);
                    });

                    // convert expression to upn
                    let upn = exprToUPN(tokens),
                        stack = [],
                        leftOp,
                        rightOp,
                        result = 0;

                    // if a valid upn array exists
                    if (upn.length) {
                        // iterate over upn array
                        upn.forEach(function (arg, index) {
                            // is number
                            if (!isNaN(arg)) {
                                stack.push(arg);
                                // is operator
                            } else if (/^\+|\-|\*|\/$/.test(arg)) {
                                rightOp = stack.pop();
                                leftOp = stack.pop();
                                stack.push(calculate(leftOp, rightOp, arg));
                            }
                        });

                        // result is still on top of stack
                        result = stack.pop();
                    }

                    return result;
                },

                /*
                 * Resets the calculator
                 */
                resetCalculator = function () {
                    expr = "";
                    updateDisplay("0");
                },

                /*
                 * Add a digit to the expression string
                 */
                addDigit = function (digit) {
                    expr += digit;
                    updateDisplay(expr);
                },

                /*
                 * Add an operator to the expression string and separates it by a space character.
                 */
                addOperator = function (op) {
                    expr += ` ${op} `;
                    updateDisplay(expr);
                };

            window.addEventListener('load', function () {
                display = document.querySelector("#calc-display div");
                operatorButtons = document.querySelectorAll("button[data-operator]");
                digitButtons = document.querySelectorAll("button[data-digit]");

                /*
                 * operator buttons
                 */
                operatorButtons.forEach(function (button) {
                    button.addEventListener('click', function (evt) {
                        addOperator(evt.target.dataset.operator);
                    });
                });

                /*
                 * digit buttons
                 */
                digitButtons.forEach(function (button) {
                    button.addEventListener('click', function (evt) {
                        addDigit(evt.target.dataset.digit);
                    });
                });

                /*
                 * decimal button
                 */
                document.querySelector("button[data-decimal]").addEventListener('click', function (evt) {
                    addDigit(evt.target.dataset.decimal);
                });

                /*
                 * result button
                 */
                document.querySelector("button[data-result]").addEventListener('click', function () {
                    // compute expression on display
                    const result = compute();
                    // start/init expression string with result
                    expr = result;
                    // display result
                    updateDisplay(result);
                });

                /*
                 * reset button
                 */
                document.querySelector("button[data-reset]").addEventListener('click', function () {
                    resetCalculator();
                });
            });
        })();
    </script>
</head>

<body>
    <div class="calc-wrapper" id="calculator">
        <div id="calc-display" class="calc-display">
            <div>0</div>
        </div>
        <table id="calc-panel" class="calc-panel">
            <tbody>
                <tr>
                    <td></td>
                    <td><button data-operator="(">(</button></td>
                    <td><button data-operator=")">)</button></td>
                    <td><button data-reset="CE">CE</button></td>
                </tr>
                <tr>
                    <td><button data-digit="7">7</button></td>
                    <td><button data-digit="8">8</button></td>
                    <td><button data-digit="9">9</button></td>
                    <td><button data-operator="/">/</button></td>
                </tr>
                <tr>
                    <td><button data-digit="4">4</button></td>
                    <td><button data-digit="5">5</button></td>
                    <td><button data-digit="6">6</button></td>
                    <td><button data-operator="*">x</button></td>
                </tr>
                <tr>
                    <td><button data-digit="1">1</button></td>
                    <td><button data-digit="2">2</button></td>
                    <td><button data-digit="3">3</button></td>
                    <td><button data-operator="-">-</button></td>
                </tr>
                <tr>
                    <td><button data-digit="0">0</button></td>
                    <td><button data-decimal=".">.</button></td>
                    <td><button data-result="=">=</button></td>
                    <td><button data-operator="+">+</button></td>
                </tr>
            </tbody>
        </table>
    </div>
    <script>
        console.log(document.getElementById("calc-panel"));
    </script>
</body>

</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment