splitAtTopLevelOnly.js 1.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
  1. /**
  2. * This splits a string on a top-level character.
  3. *
  4. * Regex doesn't support recursion (at least not the JS-flavored version).
  5. * So we have to use a tiny state machine to keep track of paren placement.
  6. *
  7. * Expected behavior using commas:
  8. * var(--a, 0 0 1px rgb(0, 0, 0)), 0 0 1px rgb(0, 0, 0)
  9. * ─┬─ ┬ ┬ ┬
  10. * x x x ╰──────── Split because top-level
  11. * ╰──────────────┴──┴───────────── Ignored b/c inside >= 1 levels of parens
  12. *
  13. * @param {string} input
  14. * @param {string} separator
  15. */ "use strict";
  16. Object.defineProperty(exports, "__esModule", {
  17. value: true
  18. });
  19. Object.defineProperty(exports, "splitAtTopLevelOnly", {
  20. enumerable: true,
  21. get: ()=>splitAtTopLevelOnly
  22. });
  23. function splitAtTopLevelOnly(input, separator) {
  24. let stack = [];
  25. let parts = [];
  26. let lastPos = 0;
  27. let isEscaped = false;
  28. for(let idx = 0; idx < input.length; idx++){
  29. let char = input[idx];
  30. if (stack.length === 0 && char === separator[0] && !isEscaped) {
  31. if (separator.length === 1 || input.slice(idx, idx + separator.length) === separator) {
  32. parts.push(input.slice(lastPos, idx));
  33. lastPos = idx + separator.length;
  34. }
  35. }
  36. if (isEscaped) {
  37. isEscaped = false;
  38. } else if (char === "\\") {
  39. isEscaped = true;
  40. }
  41. if (char === "(" || char === "[" || char === "{") {
  42. stack.push(char);
  43. } else if (char === ")" && stack[stack.length - 1] === "(" || char === "]" && stack[stack.length - 1] === "[" || char === "}" && stack[stack.length - 1] === "{") {
  44. stack.pop();
  45. }
  46. }
  47. parts.push(input.slice(lastPos));
  48. return parts;
  49. }