How do you write an arithmetic expression parser in JavaScript, without using eval or a constructor function?
Asked Answered
L

5

10

Given a string:

 var str1 = "25*5+5*7";

Without using eval or the constructor function in JavaScript, how would I be able to write a function called "output" that takes in the string and outputs the arithmetic value of the string, which in this case is 160?

Lugsail answered 24/7, 2015 at 0:7 Comment(9)
Seems like a pointless goal, especially with those apparently arbitrary constraints. What are you actually trying to do, and why?Unpolite
You'll be writing a simple arithmetic expression parser/evaluator.Roquefort
can the numbers be double digits?Briones
Yes, the number can be double digitsLugsail
If you are going to avoid "eval" or any twisted variants, you'll need to implement a parser, use that to evaluate the expression. See this SO answer for how to write a recursive descent parser; you can do this in JavaScript pretty easily: #2246462Optician
I may be wrong, but this sounds like a homework kind of question. There is nothing inherently wrong with homework questions as long as there is some attempt at a solution. Same as any other SO question.Grenadine
Closer(s): This is clearly a programming problem, and useful answers can be provided (see several here). Your close reasons are wrong.Optician
possible duplicate of Safe evaluation of arithmetic expressions in JavascriptParticularly
Does this answer your question? Evaluating a string as a mathematical expression in JavaScriptStrongarm
O
12

Here's a full precedence expression evaluator following the recursive parsing idea I linked-to in a comment on the OP's question.

To do this, first I wrote a simple BNF grammar for the expressions I wanted to process:

sum =  product | sum "+" product | sum "-" product ;
product = term | product "*" term | product "/" term ;
term = "-" term | "(" sum ")" | number ;

This by itself requires a bit of experience to do simply and straightforwardly. If you have no experience with BNF you will find it incredibly useful for describing complex streams of items like expressions, messages, programming langauges, ...

Using that grammar, I followed the procedure outlined in the other message to produce the following code. It should be obvious that it is driven by grammar in a dumb mechanical way, and therefore pretty easy to write if you have that grammar.

(Untested. I'm not a JavaScript coder. This will surely contain a few syntax/semantic hiccups. Took me at about 15 minutes to code.)

var SE="Syntax Error";

function parse(str) { // returns integer expression result or SE
   var text=str;
   var scan=1;
   return parse_sum();

   function parse_sum() { 
      var number, number2;
      if (number=parse_product()==SE) return SE;
      while (true) {
        skip_blanks();
        if (match("+") {
           number2=parse_product();
           if (number2==SE) return SE;
           number+=number2;
        }
        else if (match('-')) {
                { number2=parse_product();
                  if (number2==SE) return SE;
                  number-=number2;
                } 
             else return number;
      }
   }

   function parse_product() {
      var number, number2;
      if (number=parse_number()==SE) return SE;
      while (true) {
        if (match("*") {
            number2=parse_term();
            if (number2==SE) return SE;
            number*=number2;
          }
          else if (match('/')) {
                  number2=parse_term();
                  if (number2==SE) return SE;
                  number/=number2;
               }
               else return number; 
      }
   }

   function parse_term() {
      var number;
      skip_blanks();
      if (match("(")) {
         number=parse_sum();
         if (number=SE) return SE;
         skip_blanks();
         if (!match(")") return SE;
      }
      else if match("-") {
              number= - parse_term();
           }
           else if (number=parse_number()==SE) return SE;
      return number;
   }

   function skip_blanks() {
      while (match(" ")) { };
      return;
    }

    function parse_number() {
       number=0;
       if (is_digit()) {
          while (is_digit()) {}
          return number;
        }
        else return SE;
    }

    var number;
    function is_digit() { // following 2 lines are likely wrong in detail but not intent
       if (text[scan]>="0" && text[scan]<="9") {
          number=number*10+text[scan].toInt();
          return true;
       }
       else return false;
    }

   function match(c) {
       if (text[scan]==c)
          { scan++; return true }
       else return false;
    }
 }

It is straightforward to code such parsers/evaluators. See my SO answer on how to build a parser (which links to how to how to build an evaluator).

Optician answered 24/7, 2015 at 23:21 Comment(0)
N
4

This is a simple parser with * over + precedence. I've tried to make it as educational as possible. I'll leave it up to you to add division and subtraction. Or brackets, if you're particularly ambitious.

function parse(str) {
    var signs = ["*", "+"];             // signs in the order in which they should be evaluated
    var funcs = [multiply, add];                     // the functions associated with the signs
    var tokens = str.split(/\b/);          // split the string into "tokens" (numbers or signs)
    for (var round = 0; round < signs.length; round++) {              // do this for every sign
        document.write("tokens at this point: " + tokens.join(" ") + "<BR>");
        for (var place = 0; place < tokens.length; place++) {        // do this for every token
            if (tokens[place] == signs[round]) {                             // a sign is found
                var a = parseInt(tokens[place - 1]);        // convert previous token to number
                var b = parseInt(tokens[place + 1]);            // convert next token to number
                var result = funcs[round](a, b);               // call the appropriate function
                document.write("calculating: " + a + signs[round] + b + "=" + result + "<BR>");
                tokens[place - 1] = result.toString();          // store the result as a string
                tokens.splice(place--, 2);      // delete obsolete tokens and back up one place
            }
        }
    }
    return tokens[0];                      // at the end tokens[] has only one item: the result

    function multiply(x, y) {                       // the functions which actually do the math
        return x * y;
    }

    function add(x, y) {                            // the functions which actually do the math
        return x + y;
    }
}

var str = "25*5+5*7";
document.write("result: " + str + " = " + parse(str));
Northcliffe answered 24/7, 2015 at 1:53 Comment(4)
@IraBaxter It has * over + precedence; if you added / and - in the correct order, it would have * / + - precedence. I just wanted to get the OP started with a simple example of a parser that he may be able to understand and build upon, not show him a perfect example. So downvoting because it isn't useful "in the real world" is a bit silly.Suttles
Done. No hard feelings, I goofed; I simply missed the precedence management in your code. I thought it was too damn simple. Goes to show that elegance sometimes works against you :-{Optician
@IraBaxter Someone put a link to a question about parsers in a comment, but that question was so technical and complicated that I thought the OP might give up on the idea of trying to write a parser; I just wanted to demonstrate that you can put together something simple that works in 10 minutes.Suttles
@IraBaxter Oops, I hadn't even noticed it was you :-)Suttles
T
2

You can use the expression parser of math.js:

var str1= "25*5+5*7"
document.write(str1 + ' = ' + math.eval(str1)); 
// output: "25*5+5*7 = 160"
<script src="http://cdnjs.cloudflare.com/ajax/libs/mathjs/2.1.1/math.min.js"></script>
Tenstrike answered 25/7, 2015 at 18:11 Comment(6)
OP clearly requested an answer that didn't use eval.Optician
The answer doesn't use eval, it uses the expression parser of math.js, math.eval(expr)Tenstrike
a) its called "eval" b) it does the same thing as eval. You answer is not in the spirit of the question.Optician
Ah sorry, you are right, the function should be called "output" to follow the question. We can do that: var output = math.eval; var res = output("25*5+5*7"). Just kidding. Anyway, my answer exactly answers the question. The question itself ambiguous though. If @Lugsail measn "how can I write an expression parser myself?", your answer is the right one. But mostly these questions mean "how can I safely evaluate expressions without having to use the unsafe eval function?". That was my interpretation. In that case the expression parser of math.js is a perfect fit.Tenstrike
i can see your interpretation; perhaps you should have made that point in your answer. Objecting the eval has possibly evil side effects would be a reason to ask this question. Most people that ask questions this way seem to me to be junior enough not to understand that., and he didnt' raise that issue. So I read his question literally.Optician
ha ha, actually you did not just read his question literally, you interpreted it just as much as I did but in a different direction. The question says nothing about writing a parser, it only talks about getting the result of an expression without using eval. Anyway I guess we will never know what @Lugsail actually meant.Tenstrike
C
0

You can create a new script:

function parse(str) {
  var s = document.createElement('script');
  s.text = "window.result = " + str;
  document.body.appendChild(s); // Run script
  document.body.removeChild(s); // Clean up
  return result;                // Return the result
}
document.body.innerHTML = parse("5*5+5*5");

Or use event handler content attributes:

function parse(str) {
  var el = document.createElement('div');
  el.setAttribute('onclick', "this.result = " + str);
  el.onclick();     // Run script
  return el.result; // Return the result
}
document.body.innerHTML = parse("5*5+5*5");

Note these approaches are unsafe and as evil as eval but uglier. So I don't recommend them.

Chopin answered 24/7, 2015 at 0:11 Comment(8)
out of curiosity is result a global, can it be used later like var x=result+1;?Briones
@Briones Yes, this creates a global variable result with the result, and you can use it however you want.Chopin
Maybe I didn't define it well. Would you be able to write a function result that takes in the string and outputs the arithmetic of the string?Lugsail
@Lugsail Yes, just wrap the code in a function. See the edit.Chopin
Is there a way to do it without displaying it on the DOM? In other words, can I just do parse("5*5+5*5") in the console and it will return 50?Lugsail
This looks like a greater sin than the eval itself, because it has the same potential risk as the original eval, (may enable an injection) and modifies the DOM in addition :). eval is not evil is just misunderstood.Kurth
@tiberiu.corbu Yes, it's worse than eval, but it's not eval, so it fulfills the requirements of the question :)Chopin
@Lugsail I used document.body.innerHTML to display the result. Don't use it if you don't want it.Chopin
J
0

If you need to work with decimals you can use decimal.js-extensions-evaluate npm package which in connection with decimal.js provides great parser for expressions with decimal numbers (yes, 0.1 + 0.2 = 0.3 and not 0.30000000000000004).

Usage example:

Install both packages and use following code:

import Decimal from 'decimal.js';
import { evaluate } from 'decimal.js-extensions-evaluate';
 
evaluate.extend(Decimal);
 
let result = Decimal.evaluate('0.1 + 0.2');
 
console.log(result);                     // '0.3'
Jumna answered 14/4, 2023 at 15:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.