Создание синтаксического дерева для простых математических операций
Я пытаюсь создать синтаксическое дерево для данной строки с простыми математическими операторами ( + , -,*, / и скобками). Учитывая строку "1 + 2 * 3":
Http://img248.imageshack.us/img248/3213/misc9928.png
Он должен возвращать массив следующим образом:
["+",
[1,
["*",
[2,3]
]
]
]
Я сделал функцию для преобразования "1 + 2 * 3-внутри [1,"+",2,"*",3].
Проблема в том, что я не имею представления о том, чтобы отдавать приоритет определенным операциям.Мой код:
function isNumber(ch){
switch (ch) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '.':
return true;
break;
default:
return false;
break;
}
}
function generateSyntaxTree(text){
if (typeof text != 'string') return [];
var code = text.replace(new RegExp("[ trnvf]", "gm"), "");
var codeArray = [];
var syntaxTree = [];
// Put it in its on scope
(function(){
var lastPos = 0;
var wasNum = false;
for (var i = 0; i < code.length; i++) {
var cChar = code[i];
if (isNumber(cChar)) {
if (!wasNum) {
if (i != 0) {
codeArray.push(code.slice(lastPos, i));
}
lastPos = i;
wasNum = true;
}
} else {
if (wasNum) {
var n = Number(code.slice(lastPos, i));
if (isNaN(n)) {
throw new Error("Invalid Number");
return [];
} else {
codeArray.push(n);
}
wasNum = false;
lastPos = i;
}
}
}
if (wasNum) {
var n = Number(code.slice(lastPos, code.length));
if (isNaN(n)) {
throw new Error("Invalid Number");
return [];
} else {
codeArray.push(n);
}
}
})();
// At this moment, codeArray = [1,"+",2,"*",3]
return syntaxTree;
}
alert('Returned: ' + generateSyntaxTree("1 + 2 * 3"));
4 ответа:
Способ сделать парсер сверху вниз, если не использовать FLEX/BISON или любой другой подобный пакет, заключается в том, чтобы сначала написать токенизатор, который может анализировать входные данные и обслуживать токены.
В основном вам нужен токенизатор, который обеспечивает getNextToken, peekNextToken и skipNextToken.
Затем вы прокладываете свой путь вниз, используя эту структуру.
Как вы можете видеть, вы начинаете с запроса наименее привилегированной операции, которая требует следующей более привилегированной операции в качестве ее левого и правого члена и так далее. Унарные операторы имеют несколько иную структуру. Самое аккуратное-это рекурсия в конце, когда встречается скобка.// parser.js var input, currToken, pos; var TOK_OPERATOR = 1; var TOK_NUMBER = 2; var TOK_EOF = 3; function nextToken() { var c, tok = {}; while(pos < input.length) { c = input.charAt(pos++); switch(c) { case '+': case '-': case '*': case '/': case '(': case ')': tok.op = c; tok.type = TOK_OPERATOR; return tok; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': tok.value = c; tok.type = TOK_NUMBER; return tok; default: throw "Unexpected character: " + c; } } tok.type = TOK_EOF; return tok; } function getNextToken() { var ret; if(currToken) ret = currToken; else ret = nextToken(); currToken = undefined; return ret; } function peekNextToken() { if(!currToken) currToken = nextToken(); return currToken; } function skipNextToken() { if(!currToken) currToken = nextToken(); currToken = undefined; } function parseString(str) { input = str; pos = 0; return expression(); } function expression() { return additiveExpression(); } function additiveExpression() { var left = multiplicativeExpression(); var tok = peekNextToken(); while(tok.type == TOK_OPERATOR && (tok.op == '+' || tok.op == '-') ) { skipNextToken(); var node = {}; node.op = tok.op; node.left = left; node.right = multiplicativeExpression(); left = node; tok = peekNextToken(); } return left; } function multiplicativeExpression() { var left = primaryExpression(); var tok = peekNextToken(); while(tok.type == TOK_OPERATOR && (tok.op == '*' || tok.op == '/') ) { skipNextToken(); var node = {}; node.op = tok.op; node.left = left; node.right = primaryExpression(); left = node; tok = peekNextToken(); } return left; } function primaryExpression() { var tok = peekNextToken(); if(tok.type == TOK_NUMBER) { skipNextToken(); node = {}; node.value = tok.value; return node; } else if(tok.type == TOK_OPERATOR && tok.op == '(') { skipNextToken(); var node = expression(); // The beauty of recursion tok = getNextToken(); if(tok.type != TOK_OPERATOR || tok.op != ')') throw "Error ) expected"; return node } else throw "Error " + tok + " not exptected"; }
Вот демонстрационная страница, которая использует парсер и рендерит дерево синтаксического анализа (у него был код для него, лежащий вокруг...)
<html> <head> <title>tree</title> <script src="parser.js"></script> </head> <body onload="testParser()"> <script> function createTreeNode(x, y, val, color) { var node = document.createElement("div"); node.style.position = "absolute"; node.style.left = "" + x; node.style.top = "" + y; node.style.border= "solid"; node.style.borderWidth= 1; node.style.backgroundColor= color; node.appendChild(document.createTextNode(val)); return node; }; var yStep = 24; var width = 800; var height = 600; var RED = "#ffc0c0"; var BLUE = "#c0c0ff"; container = document.createElement("div"); container.style.width = width; container.style.height = height; container.style.border = "solid"; document.body.appendChild(container); var svgNS = "http://www.w3.org/2000/svg"; function renderLink(x1, y1, x2, y2) { var left = Math.min(x1,x2); var top = Math.min(y1,y2); var width = 1+Math.abs(x2-x1); var height = 1+Math.abs(y2-y1); var svg = document.createElementNS(svgNS, "svg"); svg.setAttribute("x", left); svg.setAttribute("y", top); svg.setAttribute("width", width ); svg.setAttribute("height", height ); var line = document.createElementNS(svgNS,"line"); line.setAttribute("x1", (x1 - left) ); line.setAttribute("x2", (x2 - left) ); line.setAttribute("y1", (y1 - top) ); line.setAttribute("y2", (y2 - top) ); line.setAttribute("stroke-width", "1"); line.setAttribute("stroke", "black"); svg.appendChild(line); var div = document.createElement("div"); div.style.position = "absolute"; div.style.left = left; div.style.top = top; div.style.width = width; div.style.height = height; div.appendChild(svg); container.appendChild(div); } function getHeight(dom) { var h = dom.offsetHeight; return h; } function getWidth(dom) { var w = dom.offsetWidth; return w; } function renderTree(x, y, node, width, height) { if(height < 1.5*yStep) height = 1.5*yStep; var val; if(node.op) { val = node.op; color = BLUE; } else if(node.value) { val = node.value; color = RED; } else val = "?"; var dom = createTreeNode(x, y, val, color); container.appendChild(dom); var w = getWidth(dom); var h = getHeight(dom); var nx, ny; var child; if(node.left) { nx = x - width/2; ny = y+height; var child = renderTree(nx, ny, node.left, width/2, height/2); renderLink(x+w/2, y+h, nx+getWidth(child)/2, ny); } if(node.right) { nx = x + width/2; ny = y+height; child = renderTree(nx, ny, node.right, width/2, height/2); renderLink(x+w/2, y+h, nx+getWidth(child)/2, ny); } return dom; } var root; function testParser() { var str = "1+2*5-5*(9+2)"; var exp = document.createElement("div"); exp.appendChild(document.createTextNode(str)); container.appendChild(exp); var tree = parseString(str); renderTree(width/2, 20, tree, width/2, 4*yStep); } </script> </body> </html>
Все, что нужно сделать, это использовать генератор синтаксического анализатора, такой как flex или ANTLR (поиск в google найдет его для вашего языка).
Но если вы делаете это для удовольствия или чтобы узнать, как работают Парсеры, посмотрите Википедию для рекурсивного спуска парсера.
Простой рекурсивный парсер спуска может быть легко сделан для простых выражений, подобных этому. Вы можете определить грамматику следующим образом:
Обратите внимание, что, делая правило для<expression> ::= <term> | <term> <add_op> <expression> <term> ::= <factor> | <factor> <mul_op> <term> <factor> ::= ( <expression> ) | <number> <add_op> ::= + | - <mul_op> ::= * | /
<term>
содержать правило для<factor>
эта грамматика гарантирует, что все операции умножения/деления происходят ниже в дереве синтаксического анализа, чем любое сложение / вычитание. Это гарантирует, что эти операции будут оценены в первую очередь.
Однажды я построил забавный маленький калькулятор, и у меня была та же проблема, что и у вас, которую я решил с помощью во-первых,построение синтаксического дерева без учета приоритета порядка. Каждый узел имеет значение приоритета, и при вычислении непостоянных значений я проверял левый узел: если он имеет меньший приоритет, я поворачивал дерево по часовой стрелке: вводил его в вычисление и оценивал его первым, аналогично для правого узла. тогда я просто попробую оценить еще раз. Мне показалось, что это сработало достаточно хорошо.