function parseExpression()

in client/src/components/special/cellprofiler/model/parameters/define-results/renderer/output-formula.js [44:171]


function parseExpression (parts = []) {
  if (parts.length === 0) {
    return [];
  }
  let i = 0;
  const variables = new Set();
  const parse = (root = true) => {
    if (parts.length <= i) {
      return [];
    }
    const result = [];
    while (i < parts.length) {
      const part = parts[i];
      i += 1;
      switch (part.toLowerCase()) {
        case '(':
          if (result.length > 0 && !result[result.length - 1].operator) {
            result.push({operator: true, value: '*'});
          }
          result.push({value: parse(false)});
          break;
        case ')':
          if (root) {
            throw new Error('Wrong expression');
          }
          return result;
        case '+':
        case '-':
        case ':':
        case '*':
        case '/':
        case '^':
          result.push({operator: true, value: part});
          break;
        default:
          const e = /^([^A-Za-z]+)([A-Za-z]+)/.exec(part);
          if (e) {
            const number = e[1];
            const variable = e[2];
            result.push({value: number});
            result.push({value: '*', operator: true});
            result.push({value: variable});
            variables.add(variable);
          } else {
            result.push({value: part});
            if (Number.isNaN(Number(part))) {
              variables.add(part);
            }
          }
          break;
      }
    }
    return result;
  };
  const parsed = parse();
  const validate = (parsedExpression) => {
    if (typeof parsedExpression === 'string') {
      return true;
    }
    if (parsedExpression.length % 2 !== 1) {
      throw new Error('Wrong expression');
    }
    if (parsedExpression[0].operator) {
      throw new Error('Wrong expression');
    }
    if (
      typeof parsedExpression[0].value !== 'string' &&
      Array.isArray(parsedExpression[0].value)
    ) {
      validate(parsedExpression[0].value);
    }
    for (let e = 1; e < parsedExpression.length - 1; e += 2) {
      if (!parsedExpression[e] || !parsedExpression[e + 1]) {
        throw new Error('Wrong expression');
      }
      if (!parsedExpression[e].operator || parsedExpression[e + 1].operator) {
        throw new Error('Wrong expression');
      }
      validate(parsedExpression[e + 1].value);
    }
    return true;
  };
  validate(parsed);
  const extract = (parsedExpression) => {
    if (typeof parsedExpression === 'string') {
      return parsedExpression;
    }
    if (typeof parsedExpression === 'object' && parsedExpression.value) {
      return extract(parsedExpression.value);
    }
    const isArray = o => typeof o !== 'string' && Array.isArray(o);
    const result = [...parsedExpression].map(o => o.value);
    if (isArray(result) && result.length === 1) {
      return extract(result[0]);
    }
    const extractByOperator = (...operator) => {
      const idx = result.findIndex(o => operator.includes(o));
      if (idx > 0) {
        let left = result[idx - 1];
        if (isArray(left)) {
          left = extract(left);
        }
        let right = result[idx + 1];
        if (isArray(right)) {
          right = extract(right);
        }
        result.splice(idx - 1, 3, {left, right, operator: result[idx]});
        extractByOperator(...operator);
      }
    };
    extractByOperator('^');
    extractByOperator('*', '/');
    extractByOperator('+', '-');
    return result[0];
  };
  const wrap = (part) => {
    if (typeof part === 'string') {
      return part;
    }
    const {
      left,
      right,
      operator
    } = part;
    return [wrap(left), operator, wrap(right)];
  };
  return {expression: wrap(extract(parsed)), variables: [...variables]};
}