代码之家  ›  专栏  ›  技术社区  ›  user216441

为简单的数学运算生成语法树

  •  8
  • user216441  · 技术社区  · 15 年前

    我正在尝试为给定的字符串生成语法树,该字符串带有简单的数学运算符(+、-、*、/,和括号)。 给定字符串“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("[ \t\r\n\v\f]", "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 回复  |  直到 15 年前
        1
  •  5
  •   Ernelli    15 年前

    如果不使用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>
    
        2
  •  1
  •   Anders Abel    15 年前

    你读过语法分析器背后的理论吗?维基百科(一如既往)有一些好文章要读:

        3
  •  1
  •   MAK    15 年前

    要做的是使用像flex或antlr这样的解析器生成器(在google上搜索会找到一个适合您的语言的解析器生成器)。

    但是,如果您这样做是为了好玩或学习解析器是如何工作的,请查阅wikipedia中的递归下降解析器。

    对于这样的简单表达式,可以很容易地创建一个简单的递归下降解析器。可以将语法定义为:

    <expression> ::= <term> | <term> <add_op> <expression>
    <term> ::= <factor> | <factor> <mul_op> <term>
    <factor> ::= ( <expression> ) | <number> 
    <add_op> ::= + | -
    <mul_op> ::= * | /
    

    注意,通过制定规则 <term> 包含的规则 <factor> 此语法确保所有乘法/除法运算在解析树中的出现率低于任何加法/减法运算。这将确保首先对这些操作进行评估。

        4
  •  0
  •   rtpg    15 年前

    我曾经做过一个有趣的小计算器,遇到了和你一样的问题,我通过 首先,在不考虑顺序优先级的情况下构建语法树。每个节点都有一个优先级值,当计算非常量时,我会检查左节点:如果它的优先级较低,我会顺时针旋转树:将其放入计算中并首先计算该值,对右节点也是如此。那我就再做一次评估。这对我来说似乎很管用。