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?

marcospereira
  • 12,045
  • 3
  • 46
  • 52
user3145336
  • 311
  • 5
  • 15
  • 2
    Seems like a pointless goal, especially with those apparently arbitrary constraints. What are you _actually_ trying to do, and why? – Lightness Races in Orbit Jul 24 '15 at 00:09
  • You'll be writing a simple arithmetic expression parser/evaluator. – Strelok Jul 24 '15 at 00:09
  • can the numbers be double digits? – depperm Jul 24 '15 at 00:10
  • Yes, the number can be double digits – user3145336 Jul 24 '15 at 00:10
  • 1
    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: http://stackoverflow.com/questions/2245962/is-there-an-alternative-for-flex-bison-that-is-usable-on-8-bit-embedded-systems/2336769#2336769 – Ira Baxter Jul 24 '15 at 00:21
  • 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. – Jon P Jul 24 '15 at 00:36
  • 2
    Closer(s): This is *clearly* a programming problem, and useful answers *can* be provided (see several here). Your close reasons are wrong. – Ira Baxter Jul 26 '15 at 09:53
  • possible duplicate of [Safe evaluation of arithmetic expressions in Javascript](http://stackoverflow.com/questions/5066824/safe-evaluation-of-arithmetic-expressions-in-javascript) –  Jul 26 '15 at 11:32
  • Does this answer your question? [Evaluating a string as a mathematical expression in JavaScript](//stackoverflow.com/q/2276021/90527) – outis Dec 24 '21 at 00:44

5 Answers5

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).

Community
  • 1
  • 1
Ira Baxter
  • 93,541
  • 22
  • 172
  • 341
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));
  • @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. – m69's been on strike for years Jul 24 '15 at 22:07
  • 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 :-{ – Ira Baxter Jul 24 '15 at 22:15
  • @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. – m69's been on strike for years Jul 24 '15 at 22:27
  • @IraBaxter Oops, I hadn't even noticed it was you :-) – m69's been on strike for years Jul 24 '15 at 22:41
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>
Jos de Jong
  • 6,602
  • 3
  • 38
  • 58
  • OP clearly requested an answer that didn't use eval. – Ira Baxter Mar 11 '16 at 08:29
  • 2
    The answer doesn't use `eval`, it uses the expression parser of math.js, `math.eval(expr)` – Jos de Jong Mar 14 '16 at 10:53
  • 3
    a) its called "eval" b) it does the same thing as eval. You answer is not in the spirit of the question. – Ira Baxter Mar 14 '16 at 11:56
  • 2
    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 @user3145336 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. – Jos de Jong Mar 14 '16 at 14:31
  • 1
    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. – Ira Baxter Mar 14 '16 at 14:37
  • 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 @user3145336 actually meant. – Jos de Jong Mar 14 '16 at 14:50
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.

Oriol
  • 274,082
  • 63
  • 437
  • 513
  • out of curiosity is result a global, can it be used later like `var x=result+1;`? – depperm Jul 24 '15 at 00:13
  • @depperm Yes, this creates a global variable `result` with the result, and you can use it however you want. – Oriol Jul 24 '15 at 00:14
  • 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? – user3145336 Jul 24 '15 at 00:16
  • @user3145336 Yes, just wrap the code in a function. See the edit. – Oriol Jul 24 '15 at 00:20
  • 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? – user3145336 Jul 24 '15 at 00:29
  • 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. – Tiberiu C. Jul 24 '15 at 00:29
  • @tiberiu.corbu Yes, it's worse than `eval`, but it's not `eval`, so it fulfills the requirements of the question :) – Oriol Jul 24 '15 at 00:33
  • @user3145336 I used `document.body.innerHTML` to display the result. Don't use it if you don't want it. – Oriol Jul 24 '15 at 00:34
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'
michal.jakubeczy
  • 8,221
  • 1
  • 59
  • 63