All files / src/shared/lib/rpn tokenizer.ts

96.67% Statements 29/30
93.75% Branches 15/16
100% Functions 4/4
96.55% Lines 28/29

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 10945x                   547x   45x 58x               233x 44x         189x 8x         181x 23x       158x 22x       136x 5x       131x 62x         69x 53x         16x 16x                     233x 9x 1x                     8x 8x       1x                       231x         45x  
import {
  isFunctionSymbol,
  isNumeric,
  toNumber,
  isOperatorSymbol,
  isVariable,
  isConstant,
} from "./helpers";
import { Token } from "./types";
 
const identity = <T>(i: T): T => i;
 
const tokenizer = (input: string): Token[] => {
  return (
    input
      .replace(/\s+/g, "")
      .split(
        /(@[a-f0-9-]{36}@|@engine::[^@]+@|<<|>>|==|!=|>=|>|<=|<|&&|\|\||[+\-*/^%&|~!@(),])/,
      )
      .filter(identity)
      .map((token): Token => {
        if (isNumeric(token)) {
          return {
            type: "VAL",
            value: toNumber(token),
          };
        }
        if (isFunctionSymbol(token)) {
          return {
            type: "FUN",
            function: token,
          };
        }
        if (token === "(") {
          return {
            type: "LBRACE",
          };
        }
        if (token === ")") {
          return {
            type: "RBRACE",
          };
        }
        if (token === ",") {
          return {
            type: "SEPERATOR",
          };
        }
        if (isOperatorSymbol(token)) {
          return {
            type: "OP",
            operator: token,
          };
        }
        if (isVariable(token)) {
          return {
            type: "VAR",
            symbol: token,
          };
        }
        if (isConstant(token)) {
          return {
            type: "CONST",
            symbol: token.replaceAll(/@/g, ""),
          };
        }
        throw new Error(`Unexpected token ${token}`);
      })
      .filter(identity) as Token[]
  )
    .map((token, i, tokens): Token[] => {
      // Handle unary negation
      if (token.type === "OP" && token.operator === "-") {
        if (i === 0) {
          return [
            {
              type: "VAL",
              value: 0,
            },
            {
              type: "OP",
              operator: "-",
            },
          ];
        }
        const previous = tokens[i - 1];
        if (
          previous.type === "LBRACE" ||
          (previous.type === "OP" && isOperatorSymbol(previous.operator))
        ) {
          return [
            {
              type: "VAL",
              value: 0,
            },
            {
              type: "OP",
              operator: "-",
            },
          ];
        }
      }
      return [token];
    })
    .flat();
};
 
export default tokenizer;