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

d4so4syb  于 2023-01-01  发布在  Java
关注(0)|答案(5)|浏览(148)

我尝试为一个给定的字符串生成一个语法树,使用简单的数学运算符(+、-、*、/和圆括号),给定字符串"1 + 2 * 3":

它应返回如下数组:

["+",
 [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"));
zhte4eai

zhte4eai1#

如果不使用FLEX/野牛或任何其他类似的包,那么执行自顶向下解析器的方法是首先编写一个可以解析输入并提供令牌的令牌化器。
基本上,您需要一个提供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>
sbtkgmzw

sbtkgmzw2#

要做的事情是使用像flex或ANTLR这样的解析器生成器(在谷歌上搜索会找到适合你语言的)。
但是如果你是为了好玩或者学习解析器是如何工作的,可以在维基百科上查找递归下降解析器。
一个简单的递归下降解析器可以很容易地为这样的简单表达式做。你可以定义语法为:

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

注意,通过使<term>的规则包含<factor>的规则,该语法确保了所有乘除操作在解析树中比任何加/减操作都更低的位置出现,这确保了这些操作首先被求值。

neekobn8

neekobn83#

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

1aaf6o9v

1aaf6o9v4#

与其他答案中的方法类似,这里是另一个递归实现。它具有以下显著特征:

  • 它生成问题中描述的嵌套数组结构。
  • 它支持带符号的数字,因此-1(没有中间空格)可以解释为文字,而不必解释为运算符。
  • 它支持一元减号,如本例中的第一个减号:它还可以接受字符串- -1--1等。
  • 它支持小数点前必须有一个数字的十进制数。
  • 它使用一个正则表达式来识别标记,将数字文本作为一个标记与任何其他非空白字符匹配。
  • 当存在语法验证错误时引发错误,并指示输入字符串中发生错误的位置。

支持的语法可以描述为:

<literal>    ::= [ '-' ] <digit> { <digit> } [ '.' { <digit> } ] ; no white space allowed
<operator2>  ::= '*' | '/'
<operator1>  ::= '+' | '-'
<factor>     ::= '-' <factor> | '(' <expression> ')' | <literal>
<term>       ::= [ <term> <operator2> ] <factor>
<expression> ::= [ <expression> <operator1> ] <term>

优先级是为了尽可能匹配作为<literal>一部分的减号。

交互式片段

x一个一个一个一个x一个一个二个一个x一个一个三个一个

解释

tokens是一个基于正则表达式的输入迭代器。该正则表达式有一个look-behindAssert,以确保减号(如果存在)不是 * binary * 运算符,并且可以包括在数字文本的匹配中。该正则表达式定义命名组,以便代码可以依赖于名称,而不必引用文本字符。
get使用此迭代器获取共享变量中的下一个标记(match)并返回前一个。get采用一个参数来指定哪个命名组需要匹配。如果确实如此,则读取下一个标记,否则get检查匹配是否是强制的。如果是,则抛出异常,否则函数返回undefined,因此呼叫者可以尝试另一个语法规则。
termfactorexpr实现了对应名称的语法规则,它们依赖于get(带参数)来决定语法规则的走向,这些函数都返回树(根节点)。
node在输出树中自下而上构造一个节点。如果树中的节点应该是不同于数组的东西,或者应该执行一些缩减(合并节点),那么这就是要更改的函数。

jjjwad0x

jjjwad0x5#

我曾经做过一个有趣的小计算器,也遇到过和你一样的问题,我通过构建语法树来解决这个问题,首先,不考虑顺序优先级,每个节点都有一个优先级值,当对非常量求值时,我会检查左边的节点:如果它的优先级较低,我会顺时针旋转树:把它放到求值中,先求值,同样的,对正确的节点,然后我再试着求值,它看起来对我来说足够好了.

相关问题