Shunting-yard and RPN algorithm visualization

Made by Michael Buen

HTML

<div ng-app ng-controller="Controller">

    
    <div ng-show='normal != null'>{{expression}}</div>
    <br/>
    
    
    <div ng-show='normal == null'>
    <input type='text' style='width: 300px' ng-model='expression' />
        <br/><br/>    
        <input type='checkbox' ng-model='yieldIntermediateResults '/> Yield Intermediate Results
        <br/></br>
    
    <input type='button' value='Structure' ng-click='structure()' />
    </div>
    
    
    <div ng-show='normal.length > 0 || opStack.length > 0'>
    <input type='button' value='Convert to Reversed Polish Notation' ng-click='convertToRpn()' />
        <br/><br/>
    </div>    
    
    <div ng-show='normal.length > 0'>
        Normal: <span class='box' ng-repeat='x in normal'>{{x}}</span>
        <br/><br/>
    </div>
    
    
    
    <div ng-show='normal.length > 0 || rpn.length > 0'>
        RPN: <span class='box' ng-repeat='x in rpn'>{{x}}</span>
        <hr/>
    </div>
    
    <div ng-show='opStack.length > 0'>
        <div class='box' style='width: 10px' ng-repeat="op in opStack.slice(0).reverse()" >{{op}}</div>
    </div>
        
    <div ng-show='yieldIntermediateResults && ((normal != null && normal.length > 0) || opStack.length > 0)'>
        __OP STACK__
        <hr/>
    </div>    


  

    

    <div ng-show="(normal.length == 00 && rpn.length > 0 && opStack.length == 0)" >
    <input type='button' value='Solve RPN' ng-click='solveRpn()'/>
        <br/>
    </div>
    
    
    <div ng-show="yieldIntermediateResults && (normal.length == 00 && rpn.length > 0 && opStack.length == 0)" >
        <div class='box' style='width: 15px' ng-repeat="v in values.slice(0).reverse()" >{{v}}</div>

        __VALUES STACK__
        <hr/>
    </div>    
    
    
    <div ng-show='rpn.length == 0 && values.length == 1'>Answer is {{values[0]}}</div>
    
</div>

CSS

.box {
    border-width: 1px;
    border-style: solid;
    margin: 2px;
    padding: 3px;
    border-color: red;
}

JavaScript

function Controller($scope) {
    
    // $scope is similar to *this* keyword of OOP, it disambiguates local variable
    // against the class-level variable
    
    // ====Model====
    
    // result: 11
    // $scope.expression = '1 + 2 * 3 - 4 + 5 * 6 / 2 - 2 * 3 ^ 2 + ( 2 + 5 ) * 2';
    $scope.expression = '2 + 3 * 4 - 5 + 7 * 6 / 3 - 2 * 3 ^ 2 + ( 5 - 2 ) * 2';
    
    // result: 16
    // $scope.expression = '1 + 2 * 3 - 4 + 5 * 6 / 2 - 2';

    
    $scope.yieldIntermediateResults = true;
    $scope.normal = null;
    
    $scope.rpn = [];  
    $scope.opStack = [];    
    
   
    $scope.values = [];
    
    
    // ====Controller's Actions====
    
    
    $scope.structure = function() {
        $scope.normal = $scope.expression.split(' ');          
    };
    
    $scope.convertToRpn = function() {
        while ((elem = $scope.normal.shift()) != null)  {
           
            
            if (elem == '(' || elem == ')') {
                if (elem == '(')
                    $scope.opStack.push(elem);
                else if (elem == ')') {
                    while((op = $scope.opStack.pop()) != '(') {
                        $scope.rpn.push(op);
                    }
                }                    
            }
            else if (!elem.isOperator()) {                
                $scope.rpn.push(elem);
            }
            else {
                
                var peek = $scope.opStack[$scope.opStack.length - 1];

                
                while ($scope.opStack.length > 0 &&
                    (
                        (elem.isLeftAssociative() && elem.operatorPrecedence() <=  peek.operatorPrecedence())
                        ||
                        elem.operatorPrecedence() < peek.operatorPrecedence()
                    )                        
                       
                      ) { 
                      
                     $scope.rpn.push($scope.opStack.pop());
                          
     ...