Как написать синтаксический анализатор арифметических выражений в 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
но страшнее Поэтому я не рекомендую их.