Echo89

Arithmetic Expression Parser

Jan 30th, 2016
161
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. var Calc = function() {
  2.     'use strict';
  3.  
  4.     this.vars = {
  5.         pi: Math.PI.toString()
  6.     };
  7. };
  8.  
  9. Calc.prototype.evaluate = function(expression) {
  10.     'use strict';
  11.  
  12.     // Trigger final instruction to execute at end.
  13.    
  14.     expression += ' ';
  15.  
  16.     // Loop Variables
  17.  
  18.     var length = expression.length,
  19.         iter,
  20.         char,
  21.         type;
  22.  
  23.     // Flags
  24.  
  25.     var flag_number = false,
  26.         flag_float = false,
  27.         flag_keyword = false,
  28.         flag_operator = false,
  29.         flag_operator_waiting = false,
  30.         flag_brackets = 0,
  31.         flag_var = false,
  32.         flag_var_maybe = false,
  33.         flag_var_name = '',
  34.         flag_math = false,
  35.         flag_math_op = '',
  36.         flag_num_consecutive = null,
  37.         flag_equals = false,
  38.         flag_bracket = false;
  39.  
  40.     // Parser Variables
  41.  
  42.     var buffer = '',
  43.         total = 0,
  44.         cur_operator = null,
  45.         buffer_parsed = 0,
  46.         prev_num = null,
  47.         bIndex,
  48.         bDepth = 0;
  49.  
  50.     // Math Functions
  51.  
  52.     var stdOps = ['log', 'tan'],
  53.         customOps = {
  54.             fact: function(value) {
  55.                 var iter,
  56.                     multiplier;
  57.  
  58.                 for(multiplier = value - 1; multiplier > 0; --multiplier) {
  59.                     value *= multiplier;
  60.                 }
  61.  
  62.                 return value;
  63.             },
  64.  
  65.             sin: function(value) {
  66.                 return Math.sin(value * Math.PI / 180);
  67.             },
  68.  
  69.             cos: function(value) {
  70.                 return Math.cos(value * Math.PI / 180);
  71.             }
  72.         };
  73.  
  74.     // Keyword Functions
  75.  
  76.  
  77.     // Helper Functions
  78.  
  79.     function isAlpha(char) {
  80.         return 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.indexOf(char) !== -1;
  81.     }
  82.  
  83.     function isNumeric(char) {
  84.         return '1234567890.,'.indexOf(char) !== -1;
  85.     }
  86.  
  87.     function isSpecial(char) {
  88.         return '+-*/^='.indexOf(char) !== -1;
  89.     }
  90.  
  91.     function applyOperator(left, right, operator) {
  92.         // Addition
  93.         if(operator === '+') {
  94.             return left + right;
  95.         }
  96.  
  97.         // Subtraction
  98.         if(operator === '-') {
  99.             return left - right;
  100.         }
  101.  
  102.         // Division
  103.         if(operator === '/') {
  104.             return left / right;
  105.         }
  106.  
  107.         // Multiplication
  108.         if(operator === '*') {
  109.             return left * right;
  110.         }
  111.  
  112.         if(operator === '^') {
  113.             return Math.pow(left, right);
  114.         }
  115.  
  116.         return null;
  117.     }
  118.  
  119.     for(iter = 0; iter < length; ++iter) {
  120.         char = expression.charAt(iter);
  121.        
  122.         if(flag_bracket) {
  123.             if(char === '(') {
  124.                 bDepth++;
  125.             }
  126.            
  127.             if(char === ')') {
  128.                 bDepth--;
  129.             }
  130.            
  131.             if(bDepth === 0) {
  132.                 //flag_bracket = false;
  133.                
  134.                 buffer = this.evaluate(expression.substring(bIndex + 1, iter));
  135.                
  136.                 if(buffer === false) {
  137.                     return false;
  138.                 }
  139.                
  140.                 // Script parses a string, not integers, so
  141.                 // conversion to string is necessary.
  142.                 buffer = buffer.toString();
  143.                
  144.                 flag_number = true;
  145.                 // Check for float
  146.                 flag_float = buffer.indexOf('.') !== -1;
  147.             } else {
  148.                 continue;
  149.             }
  150.         }
  151.  
  152.         // Are we parsing a number
  153.         if(flag_number) {
  154.             // The number has finished
  155.             if(!isNumeric(char)) {
  156.                 if(isSpecial(buffer) && !isSpecial(char)) {
  157.                     flag_number = false;
  158.                     flag_operator = false;
  159.                 }
  160.  
  161.                 // Is it a float?
  162.                 if(flag_float) {
  163.                     buffer_parsed = parseFloat(buffer, 10);
  164.                 } else {
  165.                     buffer_parsed = parseInt(buffer, 10);
  166.                 }
  167.  
  168.                 // Is there an operator waiting?
  169.                 if(flag_operator_waiting) {
  170.                     // We are setting a variable
  171.                     if(flag_var && flag_equals) {
  172.                         this.vars[flag_var_name] = buffer;
  173.                         flag_var = false;
  174.                         flag_var_name = '';
  175.                     } else {
  176.                         total = applyOperator(total, buffer_parsed, cur_operator);
  177.                        
  178.                         // console.log(total);
  179.                        
  180.                         // Turns out, we're not setting a variable, we were just referencing it.
  181.                         if(flag_var) {
  182.                             if(flag_var_name in this.vars) {
  183.                                 buffer = this.vars[flag_var_name];
  184.                                 flag_var = false;
  185.                                 flag_var_name = '';
  186.                                 flag_number = true;
  187.                                 if(buffer.indexOf('.') !== -1) {
  188.                                     flag_float = true;
  189.                                 }
  190.                                 iter--
  191.                                 continue;
  192.                             } else {
  193.                                 console.log("Invalid variable '" + flag_var_name + "'");
  194.                                 return false;
  195.                             }
  196.                         }
  197.  
  198.                         // Reset flags
  199.                         flag_number = false;
  200.                         flag_float = false;
  201.                         flag_operator_waiting = false;
  202.  
  203.                         // Unkown operator
  204.                         if(total === null) {
  205.                             process.stdout.write("Unrecognised operator '" + cur_operator + "'");
  206.                             break;
  207.                         }
  208.                     }
  209.  
  210.                     // Reset current operator
  211.                     cur_operator = '';
  212.                 } else {
  213.                     // Is a mathematical function waiting? (sin, cos, tan etc)
  214.                     if(flag_math) {
  215.                         flag_math = false;
  216.                         buffer = flag_math_op(buffer_parsed).toString();
  217.  
  218.                         // Check if float
  219.                         if(buffer.indexOf('.') !== -1) {
  220.                             flag_float = true;
  221.                         }
  222.  
  223.                         iter--;
  224.                         continue;
  225.                     }
  226.  
  227.                     // Else it's just a number, function call, or variable at the beginning of the expression
  228.                     total = buffer_parsed;
  229.                     flag_number = false;
  230.                 }
  231.  
  232.                 buffer = '';
  233.                 cur_operator = '';
  234.             } else {
  235.                 // Check for float
  236.                 if(char === '.') {
  237.                     flag_float = true;
  238.                 }
  239.  
  240.                 // Allow for commas
  241.                 if(char === ',') {
  242.                     continue;
  243.                 }
  244.  
  245.                 buffer += char;
  246.                 continue;
  247.             }
  248.         }
  249.  
  250.         if(flag_keyword) {
  251.             if(!isAlpha(char)) {
  252.                 flag_keyword = false;
  253.  
  254.                 // Case insensitivity
  255.                 buffer = buffer.toLocaleLowerCase(buffer);
  256.  
  257.                 if(buffer === 'exit') {         // exit command
  258.                     console.log('Goodbye!');
  259.                     process.exit();
  260.                 } else if(buffer === 'set') {   // Setting variable
  261.                     flag_var = true;
  262.                 } else {
  263.                     // Standard Math.[sin|cos|tan|log] operations
  264.                     if(stdOps.indexOf(buffer) !== -1) {
  265.                         flag_math = true;
  266.                         flag_math_op = Math[buffer];
  267.                     }
  268.  
  269.                     // Custom operations: fact (factorial)
  270.                     if(buffer in customOps) {
  271.                         flag_math = true;
  272.                         flag_math_op = customOps[buffer];
  273.                     }
  274.  
  275.                     // If not mathematical function, it must be a variable
  276.                     if(!flag_math) {
  277.                         // Are we setting a variable?
  278.                         if(flag_var && flag_var_name === '') {
  279.                             flag_var_name = buffer;
  280.                         } else {
  281.                             // We may be setting a variable
  282.                             flag_var_maybe = true;
  283.                             flag_var_name = buffer;
  284.                            
  285.                             // We may be referencing a variable, too.
  286.                             if(buffer in this.vars) {
  287.                                 buffer = this.vars[buffer];
  288.                                 flag_number = true;
  289.                                 // Check if variable is float
  290.                                 if(buffer.indexOf('.') !== -1) {
  291.                                     flag_float = true;
  292.                                 }
  293.                                 --iter;
  294.                                 //console.log(buffer);
  295.                                 continue;
  296.                             }
  297.                         }
  298.                     }
  299.                 }
  300.  
  301.                 buffer = '';
  302.             } else {
  303.                 buffer += char;
  304.                 continue;
  305.             }
  306.         }
  307.  
  308.         // Are we parsing an operator?
  309.         if(flag_operator) {
  310.             // Are we ending an operator?
  311.             if(!isSpecial(char) || (flag_equals && (buffer === '-' || buffer === '+')) || char === '-' || char === '+') {
  312.                 // reset flags
  313.                 flag_operator = false;
  314.                 cur_operator = buffer;
  315.  
  316.                 if(flag_equals && (buffer === '-' || buffer === '+')) {
  317.                     flag_number = true;
  318.                     continue;
  319.                 }
  320.  
  321.                 // reset buffer
  322.                 buffer = '';
  323.                 flag_operator_waiting = true;
  324.  
  325.                 if(char === '-' || char === '+') {
  326.                     flag_number = true;
  327.                     buffer += char;
  328.                     continue;
  329.                 }
  330.             } else {
  331.                 buffer += char;
  332.                 continue;
  333.             }
  334.         }
  335.  
  336.         // A number has started
  337.         if(isNumeric(char)) {
  338.             flag_number = true;
  339.             buffer += char;
  340.             continue;
  341.         }
  342.  
  343.         // A keyword, variable or function has started
  344.         if(isAlpha(char)) {
  345.             flag_keyword = true;
  346.             buffer += char;
  347.             continue;
  348.         }
  349.  
  350.         // An operator has started
  351.         if(isSpecial(char)) {
  352.             if(char === '=') {
  353.                 //console.log(char);
  354.                 flag_var_maybe = false;
  355.                 flag_var = true;
  356.                 flag_equals = true;
  357.                 continue;
  358.             } else {
  359.                 if((char === '-' || char === '+') && (flag_operator_waiting || flag_math)) {
  360.                     buffer += char;
  361.                     flag_number = true;
  362.                     continue;
  363.                 } else {
  364.                     flag_operator = true;
  365.                     buffer += char;
  366.                     continue;
  367.                 }
  368.             }
  369.         }
  370.  
  371.         // Parse parentheses
  372.         if(char === '(') {
  373.            
  374.             // Increment bracket depth
  375.             ++bDepth;
  376.             bIndex = iter;
  377.             flag_bracket = true;
  378.             continue;
  379.            
  380.             /*if(bIndex !== -1) {
  381.                 // extract and independtly evaluate this sub-expression, in a recursive fashion
  382.                 buffer = this.evaluate(expression.substring(iter + 1, bIndex));
  383.                 if(buffer === false) {
  384.                     return false;
  385.                 }
  386.                 buffer = buffer.toString();
  387.                 flag_number = true;
  388.                 flag_float = buffer.indexOf('.') !== -1;
  389.                 iter = bIndex;
  390.  
  391.                 continue;
  392.             } else {
  393.                 console.log("Invalid bracket syntax");
  394.                 return false;
  395.             }*/
  396.         }
  397.        
  398.         if(char === ')' && flag_bracket) {
  399.             flag_bracket = false;
  400.             continue;
  401.         }
  402.        
  403.         if(char !== ' ' && char !== "\n" && char !== '\r') {
  404.             console.log("Unexpected character '" + char + "'");
  405.             return false;
  406.         }
  407.     }
  408.    
  409.     // We are setting a variable
  410.     if(flag_var || flag_var_maybe) {
  411.         if(!flag_equals) {
  412.             if(!(flag_var_name in this.vars)) {
  413.                 console.log("Invalid variable '" + flag_var_name + "'");
  414.                 return false;
  415.             }
  416.         }
  417.         this.vars[flag_var_name] = total.toString();
  418.     }
  419.    
  420.     if(flag_bracket) {
  421.         console.log("Invalid bracket syntax");
  422.         return false;
  423.     }
  424.  
  425.     return total;
  426. };
  427.  
  428. var calc = new Calc();
  429.  
  430. process.stdin.resume();
  431. process.stdin.setEncoding('utf8');
  432. process.stdin.on('data', function(expression) {
  433.     var result = calc.evaluate(expression);
  434.     if(result !== false) {
  435.         console.log(result)
  436.     }
  437.     process.stdout.write("> ");
  438. });
  439. console.log("Welcome to calc.js:");
  440. process.stdout.write("> ");
RAW Paste Data