Как написать синтаксический анализатор арифметических выражений в JavaScript без использования eval или функции конструктора?

Учитывая строку:

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

Без использования eval или функцию конструктора в JavaScript, как бы я мог написать функцию с именем "output", которая принимает строку и выводит арифметическое значение строки, которое в данном случае равно 160?

5 ответов

Вот полный оценщик выражений предшествования, следуя идее рекурсивного анализа, с которой я связался в комментарии к вопросу OP.

Для этого сначала я написал простую грамматику BNF для выражений, которые хотел обработать:

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

Это само по себе требует немного опыта, чтобы сделать это просто и понятно. Если у вас нет опыта работы с BNF, вы найдете его невероятно полезным для описания сложных потоков элементов, таких как выражения, сообщения, языки программирования,...

Используя эту грамматику, я следовал процедуре, описанной в другом сообщении, чтобы создать следующий код. Должно быть очевидно, что он приводится в действие грамматикой тупым механическим способом, и поэтому довольно легко написать, если у вас есть эта грамматика.

(Не проверено. Я не кодер JavaScript. Это, безусловно, будет содержать несколько синтаксических / семантических ошибок. Мне потребовалось около 15 минут, чтобы написать код.)

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;
    }
 }

Кодировать такие парсеры / оценщики просто. Смотрите мой SO-ответ о том, как создать синтаксический анализатор (который ссылается на то, как создать оценщик).

Это простой парсер с * над + приоритетом. Я пытался сделать это как можно более образовательным. Я оставлю это на ваше усмотрение, чтобы добавить деление и вычитание. Или скобки, если вы особенно амбициозны.

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
        alert("tokens at this point: " + tokens.join(" "));
        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
                alert("calculating: " + a + signs[round] + b + "=" + result);
                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";
alert("result: " + str + " = " + parse(str));

Вы можете использовать синтаксический анализатор выражения 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>

Если вам нужно работать с десятичными числами, вы можете использовать пакет npm decimal.js-extensions-evaluate , который в сочетании с decimal.js обеспечивает отличный анализатор выражений с десятичными числами (да,0.1 + 0.2 = 0.3и не0.30000000000000004).

Пример использования:

Установите оба пакета и используйте следующий код:

      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'

Вы можете создать новый скрипт:

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");

Или используйте атрибуты содержимого обработчика событий:

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");

Обратите внимание, что эти подходы небезопасны и злы как eval но страшнее Поэтому я не рекомендую их.

Другие вопросы по тегам