Calculator using Shunting yard algorithm in HTML using JavaScript

When delving into JavaScript, crafting a calculator serves as an initial challenge worth embracing. Various approaches exist for constructing a calculator, such as leveraging eval, loops, and more. The website already hosts tutorials on developing a calculator through eval and utilizing for loops. Nonetheless, these methods may not be the most optimal due to security vulnerabilities associated with eval and the heightened code complexity stemming from increased loop iterations. This guide sheds light on a more effective technique for calculator development. We will delve into employing the Shunting Yard algorithm credited to Dijkstra for tackling calculator expressions, employing not just one but two stacks.

Simple Introduction to HTML, CSS and JS:

  • HTML is the markup language used to structure a webpage.
  • CSS is the styling language used to design the layout and look of the elements of the webpage.
  • JS is the scripting language used to define the behavior and working of the elements in the webpage.

Simple introduction to Shunting Yard Algorithm:

Typically, this algorithm is utilized for transforming an infix expression into postfix, which is also recognized as Reverse Polish Notation (RPN). In infix notation, an expression appears as "a + b", whereas in postfix notation, it is represented as "ab+". The evaluation of a postfix expression is straightforward, and usually, a computer converts an infix expression into postfix using stacks to calculate the result.

Steps to convert infix to Postfix:

  • Read the given expression from left to right. If the scanned element is an operand, simply append it to the resultant postfix expression.
  • If the scanned operator is an operator and the stack is empty, push the operator into the stack.
  • If the scanned element is an operator and the precedence of the operator is greater than or equal to the precedence of the operator on the top of the stack, push it into the stack.
  • If the scanned operator is an operator and the precedence of the scanned operator is less than the precedence of the operator on the top of the stack, pop the operator on the top of the stack and add it to the postfix expression until the stack becomes empty or the precedence of the operator on the top of the stack is less than the precedence of the scanned operator. Now, push the scanned operator into the stack.

For example: 3 + 8 - 9 / 8

Scanned element Stack Postfix notation
3 - 3
+ + 3
8 + 3 8
- - 3 8 +
9 - 3 8 + 9
/ -/ 3 8 + 9
8 -/ 3 8 + 9 8
Postfix expression: 3 8 + 9 8 / -

Evaluating a postfix expression:

Example:

Consider the postfix notation provided above. Let's begin by obtaining the outcome from the infix notation:

3 + 8 - 9 / 8

= 3 + 8 - 1.125

= 11 - 1.125

The result of the calculation is 9.875.

The order of evaluation follows the rules of a scientific calculator, which is based on BODMAS.

Next, let's consider the postfix expression derived from the preceding conversion:

3 8 + 9 8 / -

  • Read the expression from left to right, initialize a stack with the same length as the expression.
  • If the element encountered is an operand, push it into the stack.
  • If the element encountered is an operator, pop two operands a and b from the stack, apply the operator (b operator a) and push the result back into the stack.
  • In the above example: 3 8 + 9 8 / -

In order to construct a calculator utilizing the Shunting Yard algorithm, we will employ a pair of stacks. This is necessary as we have to handle both the conversion to postfix notation and the subsequent evaluation of the postfix expression.

Sample HTML and CSS for the Calculator:

Example

<!DOCTYPE html>

<html lang="">

<head>

    <meta charset="utf-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>calci</title>

</head>



<body>

    <center>

    <h1>Calculator</h1>

     <table>

       <tr><input id = "display"></tr>

       <tr><td> <button onclick = fun(7) class = "num">7</button></td>

           <td> <button onclick = fun(8) class = "num">8</button></td>

           <td> <button onclick = fun(9) class = "num">9</button></td>

           <td></td>

           <td> <button onclick = C()>C</button></td>

           <td> <button onclick = B()>B</button></td>

       </tr>

       <tr><td> <button onclick = fun(4) class = "num">4</button></td>

           <td> <button onclick = fun(5) class = "num">5</button></td>

           <td> <button onclick = fun(6) class = "num">6</button></td>

           <td></td>

           <td><button onclick = fun("*")>*</button></td>

            <td><button onclick = fun("/")>/</button></td>

       </tr>

       <tr><td> <button onclick = fun(1) class = "num">1</button></td>

           <td> <button onclick = fun(2) class = "num">2</button></td>

           <td> <button onclick = fun(3) class = "num">3</button></td>

           <td></td>

           <td> <button onclick = fun("+")>+</button></td>

           <td> <button onclick = fun("-")>-</button></td>

       </tr>

         <tr>

           <td> <button onclick = fun("(")>(</button></td>

             <td> <button onclick = fun(0) class = "num">0</button></td>

           <td> <button onclick = fun(")")>)</button></td>

           <td></td>

             <td> <button onclick = fun(".")>.</button></td>

           <td> <button onclick = res()>=</button></td>

       </tr>

    </table>

    </center>

    <p id = "hi"></p>

    <p id = "hi1"></p>

   <div id = "footer">By: Jeevani Anumandla, Hyderabad</div></center>

</body>

</html>
Example

<style>

        table {

            background-color: brown;

            border: 1px solid black;

            table-layout: fixed;

            border: 1px solid black;

            border-spacing: 10px;

            box-shadow: 0 12px 16px 0 rgba(0,0,0,0.24), 0 17px 50px 0 rgba(0,0,0,0.19);

            border-radius: 10px;

        }

        button{

            padding: 16px;

            border-radius: 8px;

        }

        button:hover{

            background-color: black;

            color: white; 

            

        }

        button:active{

            transform: scale(0.98);

            box-shadow:3px 2px 22px 1px rgba(0, 0, 0, 0.24);

        }

        #display{

            width:290px;

            height:40px;

            margin: 10px 0;

            box-shadow: 0 12px 16px 0 rgba(0,0,0,0.24), 0 17px 50px 0 rgba(0,0,0,0.19);

            border-radius: 10px;

        }

        #display:hover{

            background-color: black;

            color: white;

        }

        body{

            background-color: black;

        }

        h1{

            color: white;

        }

        body:hover{

            background-color: floralwhite;

        }

        body:hover h1{

            color: black;

        }

#footer{

          position: fixed;

          padding: 7px 7px 7px 7px;

          bottom: 0;

          width: 100%;

          height: 20px;

          background: grey;

      }

    </style>

JavaScript:

  • On clicking numbers or operators, the value of the button must be displayed on the display.
  • On clicking on C, the display has to become empty.
  • On clicking B, the last element on the display must be deleted.
  • On clicking =, everything on the display must be solved and now the display has to show the result of the expression entered.

Consequently, it is necessary to create four JavaScript functions to handle the operations of the four distinct button types.

You have the flexibility to customize the appearance of the calculator using CSS. In this case, we will opt for a straightforward approach without incorporating elaborate styling options. The calculator will be structured similarly to the one demonstrated in the tutorial that leveraged eval for its functionality.

We'll make a calculator like this:

  • The code was created using io. Feel free to utilize a text editor or any preferred software for this purpose.

Code:

Instead of presenting the entire code at one go, let's divide it into smaller parts. Let's begin with the section related to displaying:

Example

<script>

   function fun(b)

    {

        document.getElementById("display").value+=b;

    }

    function C()

    {

        document.getElementById("display").value="";

    }

     function B()

    {

        var a = document.getElementById("display").value;

        document.getElementById("display").value=a.slice(0, a.length-1);

    }

    function res()

    {

        var a = document.getElementById("display").value;

        a = a.trim();

        document.getElementById("display").value=fun1(a);

   }
  • fun(a): On clicking all the buttons of numbers and operators, we call fun with the value of the number or operator as argument. In the function, we need to print that value on the display input box, hence, we concatenate the argument to the value already in the input box.
  • C: We'll simply reassign the value of the display input box to an empty string to clear the display.
  • B: We'll get the whole string on the display and slice it deleting the last element and re-assign it to the display again.
  • res: This function is activated on clicking =.
  • On clicking all the buttons of numbers and operators, we call fun with the value of the number or operator as argument.
  • In the function, we need to print that value on the display input box, hence, we concatenate the argument to the value already in the input box.

By utilizing the trim function, we eliminate any surplus white spaces before passing the resulting expression as an argument to the function fun1.

fun1:

The function encapsulates the core operations or the backend logic of the calculator once it receives the expression as an input.

Code:

Example

function fun1(a)

    {

        var i=0;

        const values = [];

        const operators = [];

        var opc=0, clc=0;

        for(i=0;i<a.length;i++)

            {

                if(a[i]=="(")

                    {

                        opc+=1;

                    }

                if(a[i]==")")

                    {

                        clc+=1;

                    }

            }

        if(opc!=clc)

            {

                return "Invalid paranthesis";

            }

        if(opc>0)

            {

               var op = a.indexOf("(");

               var cl = a.lastIndexOf(")");

               var x = a.slice(0, op);

               var y = fun1(a.slice(op+1, cl));

               var z = a.slice(cl+1, a.length);

               var b = x + y + z;

               return fun1(b);

            }

        if(opc==0)

        {

            for(var i = 0;i<a.length;i++)

                {

                    if(i==0 && (a[0]=="+" || a[0]=="-"))

                        {

                            values.push(0);

                        }

                    if((a[i]=="+" || a[i]=="-"|| a[i]=="*" || a[i]=="/") && (a[i+1]=="*" || a[i+1]=="/" || a[i+1]=="+"))

                        {

                            return "Invalid statement";

                        }

                    if(a[i]=="+" || a[i]=="-" || a[i]=="*" || a[i]=="/")

                    {

                        

                        if(operators.length==0 || (precedence(operators[operators.length-1])<precedence(a[i])))

                        {

                            operators.push(a[i]);

                        }

                        else if(precedence(operators[operators.length-1])>=precedence(a[i]))

                        {

                             var f, s;

                             f = parseFloat(values.pop());

                             s = parseFloat(values.pop());

                             if(operators[operators.length-1]=="+")

                                 {

                                     values.push(s+f);

                                 }

                            if(operators[operators.length-1]=="-")

                                 {

                                     values.push(s-f);

                                 }

                            if(operators[operators.length-1]=="*")

                                 {

                                     values.push(s*f);

                                 }

                            if(operators[operators.length-1]=="/")

                                 {

                                     values.push(s/f);

                                 }

                            operators.pop();

                            operators.push(a[i]);

                        }

                    }

                   if((a[i]=="+" || a[i]=="-"|| a[i]=="*" || a[i]=="/") && (a[i+1]=="-"))

                        {

                            var ch="";

                            var j;

                            for(j=i+1;j<a.length;j++)

                            {

                                if(a[j+1]=="+" || a[j+1]=="-" || a[j+1]=="*" || a[j+1]=="/")

                                {

                                    ch+=a[j];

                                    break;

                                }

                                ch+=a[j];

                            }   

                            values.push(parseFloat(ch));

                            i+=ch.length;

                        }

                    else if(a[i]!="+" && a[i]!="-"&& a[i]!="*" && a[i]!="/")

                    {

                        var ch="";

                        var j;

                        for(j=i;j<a.length;j++)

                        {

                            if(a[j+1]=="+" || a[j+1]=="-" || a[j+1]=="*" || a[j+1]=="/")

                            {

                                ch+=a[j];

                                break;

                            }

                            ch+=a[j];

                        }   

                        values.push(parseFloat(ch));

                        i+=ch.length-1;

                    }

                }

            }

            while(operators.length!=0)

            {

                             var f, s;

                             f = parseFloat(values.pop());

                             s = parseFloat(values.pop());

                             if(operators[operators.length-1]=="+")

                                 {

                                     values.push(s+f);

                                 }

                            if(operators[operators.length-1]=="-")

                                 {

                                     values.push(s-f);

                                 }

                            if(operators[operators.length-1]=="*")

                                 {

                                     values.push(s*f);

                                 }

                            if(operators[operators.length-1]=="/")

                                 {

                                     values.push(s/f);

                                 }

                            operators.pop();

            }

        return values;

    }

    function precedence(a)

    {

        if(a=="+")

            {

                return 1;

            }

        if(a=="-")

            {

                return 2;

            }

        if(a=="*")

            {

                return 3;

            }

        if(a=="/")

            {

                return 4;

            }

    }

</script>

Explanation:

  • We declared two arrays (stacks), values and operators.
  • opc and clc stand for opening braces and closing braces.
  • In the next for loop, we've checked if the number of opening braces is equal to the number of closing braces. Else, it returns that the expression provided is invalid.
  • According to BODMAS, we need to first solve the sub-expressions inside brackets, hence, we check if there are any brackets and if there are, we extract the sub-expression between the brackets and call fun1 again with the sub-expression as input.
  • This recursion continues if there are any brackets in the expression.
  • Now, coming to the actual solving part: We check if there is a preceding sign to the expression, if there is any, we push a 0 into values. Suppose, if expression is -8: 0-8 is still -8. Then, we checked if there are consecutive operators which return invalid expression again. If there is an operand, push it into the values stack Now, if there is an operator: If the operators stack is empty, push the operator into the stack If the precedence of the operator found is greater than the precedence of the operator on the top of the operators stack, push the operator into the stack If the precedence of the operator found is less than or equal to the operator on the top of the operators stack, pop the last operator from the operators stack and two operands from the values stack and perform the operation on the two operands and push the result into the values stack. Continue the process until the precedence of the operator found is greater than the precedence of the operator on the top of the stack.
  • Now, we handled a situation where + and - can be consecutive. This usually occurs where there are brackets: 3 + (2 - 3) -> 3 + - 1 -> 2
  • In that scenario, we get the operand along with the - sign, convert it into float and push it into values stack like a regular operand.
  • Finally, if the operators stack isn't empty which means there are still a few operators and values left, pop out the operands from the values stack and apply the operator on the top of the operators stack and pop the operator from the operators stack.
  • Continue the process until there are no operators and operands left in the operators and values stack.
  • The function precedence is to manage the precedence rules as in BODMAS/ PEDMAS
  • We check if there is a preceding sign to the expression, if there is any, we push a 0 into values. Suppose, if expression is -8: 0-8 is still -8.
  • Then, we checked if there are consecutive operators which return invalid expression again.
  • If there is an operand, push it into the values stack
  • Now, if there is an operator: If the operators stack is empty, push the operator into the stack If the precedence of the operator found is greater than the precedence of the operator on the top of the operators stack, push the operator into the stack If the precedence of the operator found is less than or equal to the operator on the top of the operators stack, pop the last operator from the operators stack and two operands from the values stack and perform the operation on the two operands and push the result into the values stack. Continue the process until the precedence of the operator found is greater than the precedence of the operator on the top of the stack.
  • If the operators stack is empty, push the operator into the stack
  • If the precedence of the operator found is greater than the precedence of the operator on the top of the operators stack, push the operator into the stack
  • If the precedence of the operator found is less than or equal to the operator on the top of the operators stack, pop the last operator from the operators stack and two operands from the values stack and perform the operation on the two operands and push the result into the values stack.
  • Continue the process until the precedence of the operator found is greater than the precedence of the operator on the top of the stack.

Example:

Expression: 3 + (5 + 9 * 9)

  • The expression between the parenthesis is passed again to the function: 5 is pushed into the values stack + is pushed into the operators stack 9 is pushed into the values stack The precedence of is greater than +, so, it is pushed into the values stack 9 is pushed into the values stack. Values: Operators: Pop two operands from values stack: 9 9 and apply the operator at the top of the operators stack: 9 9 = 81. Push 81 into the values stack: 81 + 5 = 86.
  • Now, 86 is concatenated to the original string in the place of the expression within parenthesis
  • Expression: 3 + 86
  • 3 is pushed into the values stack.
  • + is pushed into the operators stack
  • 86 is pushed into the values stack
  • 86 + 3 = 89 is the final result .
  • 5 is pushed into the values stack
  • + is pushed into the operators stack
  • 9 is pushed into the values stack
  • The precedence of * is greater than +, so, it is pushed into the values stack
  • 9 is pushed into the values stack. Values: Operators:
  • Pop two operands from values stack: 9 9 and apply the operator at the top of the operators stack: 9 * 9 = 81. Push 81 into the values stack: 81 + 5 = 86.

Output Screens:

Input Required

This code uses input(). Please provide values below: