parse()

in src/core/xfa/formcalc_parser.js [277:469]


  parse(tok) {
    tok ||= this.lexer.next();

    while (true) {
      // Token ids (see form_lexer.js) are consecutive in order
      // to have switch table with no holes.
      switch (tok.id) {
        case TOKEN.and:
          if (this.last === OPERAND) {
            this.pushOperator(Operators.and);
            break;
          }
          return [tok, this.getNode()];
        case TOKEN.divide:
          if (this.last === OPERAND) {
            this.pushOperator(Operators.div);
            break;
          }
          return [tok, this.getNode()];
        case TOKEN.dot:
          if (this.last === OPERAND) {
            this.pushOperator(Operators.dot);
            break;
          }
          return [tok, this.getNode()];
        case TOKEN.dotDot:
          if (this.last === OPERAND) {
            this.pushOperator(Operators.dotDot);
            break;
          }
          return [tok, this.getNode()];
        case TOKEN.dotHash:
          if (this.last === OPERAND) {
            this.pushOperator(Operators.dotHash);
            break;
          }
          return [tok, this.getNode()];
        case TOKEN.dotStar:
          if (this.last === OPERAND) {
            this.pushOperator(Operators.dot);
            this.pushOperand(new AstEveryOccurence());
            break;
          }
          return [tok, this.getNode()];
        case TOKEN.eq:
          if (this.last === OPERAND) {
            this.pushOperator(Operators.eq);
            break;
          }
          return [tok, this.getNode()];
        case TOKEN.ge:
          if (this.last === OPERAND) {
            this.pushOperator(Operators.ge);
            break;
          }
          return [tok, this.getNode()];
        case TOKEN.gt:
          if (this.last === OPERAND) {
            this.pushOperator(Operators.gt);
            break;
          }
          return [tok, this.getNode()];
        case TOKEN.le:
          if (this.last === OPERAND) {
            this.pushOperator(Operators.le);
            break;
          }
          return [tok, this.getNode()];
        case TOKEN.leftBracket:
          if (this.last === OPERAND) {
            this.flushWithOperator(Operators.subscript);
            const operand = this.operands.pop();
            const index = SimpleExprParser.parseIndex(this.lexer);
            this.operands.push(new AstSubscript(operand, index));
            this.last = OPERAND;
            break;
          }
          return [tok, this.getNode()];
        case TOKEN.leftParen:
          if (this.last === OPERAND) {
            const lastOperand = this.operands.at(-1);
            if (!(lastOperand instanceof AstIdentifier)) {
              return [tok, this.getNode()];
            }
            lastOperand.toLowerCase();
            const name = lastOperand.id;

            this.flushWithOperator(Operators.call);
            const callee = this.operands.pop();
            const params = SimpleExprParser.parseParams(this.lexer);

            if (callee instanceof AstIdentifier && BUILTINS.has(name)) {
              this.operands.push(new AstBuiltinCall(name, params));
            } else {
              this.operands.push(new AstCall(callee, params));
            }

            this.last = OPERAND;
          } else {
            this.operators.push(Operators.paren);
            this.last = OPERATOR;
          }
          break;
        case TOKEN.lt:
          if (this.last === OPERAND) {
            this.pushOperator(Operators.lt);
            break;
          }
          return [tok, this.getNode()];
        case TOKEN.minus:
          if (this.last === OPERATOR) {
            this.pushOperator(Operators.minus);
          } else {
            this.pushOperator(Operators.sub);
          }
          break;
        case TOKEN.ne:
          if (this.last === OPERAND) {
            this.pushOperator(Operators.ne);
            break;
          }
          return [tok, this.getNode()];
        case TOKEN.not:
          if (this.last === OPERAND) {
            this.pushOperator(Operators.not);
            break;
          }
          return [tok, this.getNode()];
        case TOKEN.null:
          if (this.last === OPERATOR) {
            this.pushOperand(new AstNull());
            break;
          }
          return [tok, this.getNode()];
        case TOKEN.number:
          if (this.last === OPERATOR) {
            this.pushOperand(new AstNumber(tok.value));
            break;
          }
          return [tok, this.getNode()];
        case TOKEN.or:
          if (this.last === OPERAND) {
            this.pushOperator(Operators.or);
            break;
          }
          return [tok, this.getNode()];
        case TOKEN.plus:
          if (this.last === OPERATOR) {
            this.pushOperator(Operators.plus);
          } else {
            this.pushOperator(Operators.add);
          }
          break;
        case TOKEN.rightBracket:
          if (!this.flushUntil(Operators.subscript.id)) {
            return [tok, this.getNode()];
          }
          break;
        case TOKEN.rightParen:
          if (!this.flushUntil(Operators.paren.id)) {
            return [tok, this.getNode()];
          }
          break;
        case TOKEN.string:
          if (this.last === OPERATOR) {
            this.pushOperand(new AstString(tok.value));
            break;
          }
          return [tok, this.getNode()];
        case TOKEN.this:
          if (this.last === OPERATOR) {
            this.pushOperand(new AstThis());
            break;
          }
          return [tok, this.getNode()];
        case TOKEN.times:
          if (this.last === OPERAND) {
            this.pushOperator(Operators.mul);
            break;
          }
          return [tok, this.getNode()];
        case TOKEN.identifier:
          if (this.last === OPERATOR) {
            this.pushOperand(new AstIdentifier(tok.value));
            break;
          }
          return [tok, this.getNode()];
        default:
          return [tok, this.getNode()];
      }
      tok = this.lexer.next();
    }
  }