/* @license
 * Copyright 2019 Google LLC. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
const numberNode = (value, unit) => ({ type: "number", number: value, unit });
const parseExpressions = (() => {
  const cache = {};
  const MAX_PARSE_ITERATIONS = 1e3;
  return (inputString) => {
    const cacheKey = inputString;
    if (cacheKey in cache) {
      return cache[cacheKey];
    }
    const expressions = [];
    let parseIterations = 0;
    while (inputString) {
      if (++parseIterations > MAX_PARSE_ITERATIONS) {
        inputString = "";
        break;
      }
      const expressionParseResult = parseExpression(inputString);
      const expression = expressionParseResult.nodes[0];
      if (expression == null || expression.terms.length === 0) {
        break;
      }
      expressions.push(expression);
      inputString = expressionParseResult.remainingInput;
    }
    return cache[cacheKey] = expressions;
  };
})();
const parseExpression = (() => {
  const IS_IDENT_RE = /^(\-\-|[a-z\u0240-\uffff])/i;
  const IS_OPERATOR_RE = /^([\*\+\/]|[\-]\s)/i;
  const IS_EXPRESSION_END_RE = /^[\),]/;
  const FUNCTION_ARGUMENTS_FIRST_TOKEN = "(";
  const HEX_FIRST_TOKEN = "#";
  return (inputString) => {
    const terms = [];
    while (inputString.length) {
      inputString = inputString.trim();
      if (IS_EXPRESSION_END_RE.test(inputString)) {
        break;
      } else if (inputString[0] === FUNCTION_ARGUMENTS_FIRST_TOKEN) {
        const { nodes, remainingInput } = parseFunctionArguments(inputString);
        inputString = remainingInput;
        terms.push({
          type: "function",
          name: { type: "ident", value: "calc" },
          arguments: nodes
        });
      } else if (IS_IDENT_RE.test(inputString)) {
        const identParseResult = parseIdent(inputString);
        const identNode = identParseResult.nodes[0];
        inputString = identParseResult.remainingInput;
        if (inputString[0] === FUNCTION_ARGUMENTS_FIRST_TOKEN) {
          const { nodes, remainingInput } = parseFunctionArguments(inputString);
          terms.push({ type: "function", name: identNode, arguments: nodes });
          inputString = remainingInput;
        } else {
          terms.push(identNode);
        }
      } else if (IS_OPERATOR_RE.test(inputString)) {
        terms.push({ type: "operator", value: inputString[0] });
        inputString = inputString.slice(1);
      } else {
        const { nodes, remainingInput } = inputString[0] === HEX_FIRST_TOKEN ? parseHex(inputString) : parseNumber(inputString);
        if (nodes.length === 0) {
          break;
        }
        terms.push(nodes[0]);
        inputString = remainingInput;
      }
    }
    return { nodes: [{ type: "expression", terms }], remainingInput: inputString };
  };
})();
const parseIdent = (() => {
  const NOT_IDENT_RE = /[^a-z0-9_\-\u0240-\uffff]/i;
  return (inputString) => {
    const match = inputString.match(NOT_IDENT_RE);
    const ident = match == null ? inputString : inputString.substr(0, match.index);
    const remainingInput = match == null ? "" : inputString.substr(match.index);
    return { nodes: [{ type: "ident", value: ident }], remainingInput };
  };
})();
const parseNumber = (() => {
  const VALUE_RE = /[\+\-]?(\d+[\.]\d+|\d+|[\.]\d+)([eE][\+\-]?\d+)?/;
  const UNIT_RE = /^[a-z%]+/i;
  const ALLOWED_UNITS = /^(m|mm|cm|rad|deg|[%])$/;
  return (inputString) => {
    const valueMatch = inputString.match(VALUE_RE);
    const value = valueMatch == null ? "0" : valueMatch[0];
    inputString = value == null ? inputString : inputString.slice(value.length);
    const unitMatch = inputString.match(UNIT_RE);
    let unit = unitMatch != null && unitMatch[0] !== "" ? unitMatch[0] : null;
    const remainingInput = unitMatch == null ? inputString : inputString.slice(unit.length);
    if (unit != null && !ALLOWED_UNITS.test(unit)) {
      unit = null;
    }
    return {
      nodes: [{
        type: "number",
        number: parseFloat(value) || 0,
        unit
      }],
      remainingInput
    };
  };
})();
const parseHex = (() => {
  const HEX_RE = /^[a-f0-9]*/i;
  return (inputString) => {
    inputString = inputString.slice(1).trim();
    const hexMatch = inputString.match(HEX_RE);
    const nodes = hexMatch == null ? [] : [{ type: "hex", value: hexMatch[0] }];
    return {
      nodes,
      remainingInput: hexMatch == null ? inputString : inputString.slice(hexMatch[0].length)
    };
  };
})();
const parseFunctionArguments = (inputString) => {
  const expressionNodes = [];
  inputString = inputString.slice(1).trim();
  while (inputString.length) {
    const expressionParseResult = parseExpression(inputString);
    expressionNodes.push(expressionParseResult.nodes[0]);
    inputString = expressionParseResult.remainingInput.trim();
    if (inputString[0] === ",") {
      inputString = inputString.slice(1).trim();
    } else if (inputString[0] === ")") {
      inputString = inputString.slice(1);
      break;
    }
  }
  return { nodes: expressionNodes, remainingInput: inputString };
};
const $visitedTypes = Symbol("visitedTypes");
class ASTWalker {
  constructor(visitedTypes) {
    this[$visitedTypes] = visitedTypes;
  }
  /**
   * Walk the given set of ASTs, and invoke the provided callback for nodes that
   * match the filtered set that the ASTWalker was constructed with.
   */
  walk(ast, callback) {
    const remaining = ast.slice();
    while (remaining.length) {
      const next = remaining.shift();
      if (this[$visitedTypes].indexOf(next.type) > -1) {
        callback(next);
      }
      switch (next.type) {
        case "expression":
          remaining.unshift(...next.terms);
          break;
        case "function":
          remaining.unshift(next.name, ...next.arguments);
          break;
      }
    }
  }
}
const ZERO = Object.freeze({ type: "number", number: 0, unit: null });
export {
  ASTWalker,
  ZERO,
  numberNode,
  parseExpressions
};
//# sourceMappingURL=parsers.js.map
