322 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			322 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
var openParentheses = "(".charCodeAt(0);
 | 
						|
var closeParentheses = ")".charCodeAt(0);
 | 
						|
var singleQuote = "'".charCodeAt(0);
 | 
						|
var doubleQuote = '"'.charCodeAt(0);
 | 
						|
var backslash = "\\".charCodeAt(0);
 | 
						|
var slash = "/".charCodeAt(0);
 | 
						|
var comma = ",".charCodeAt(0);
 | 
						|
var colon = ":".charCodeAt(0);
 | 
						|
var star = "*".charCodeAt(0);
 | 
						|
var uLower = "u".charCodeAt(0);
 | 
						|
var uUpper = "U".charCodeAt(0);
 | 
						|
var plus = "+".charCodeAt(0);
 | 
						|
var isUnicodeRange = /^[a-f0-9?-]+$/i;
 | 
						|
 | 
						|
module.exports = function(input) {
 | 
						|
  var tokens = [];
 | 
						|
  var value = input;
 | 
						|
 | 
						|
  var next,
 | 
						|
    quote,
 | 
						|
    prev,
 | 
						|
    token,
 | 
						|
    escape,
 | 
						|
    escapePos,
 | 
						|
    whitespacePos,
 | 
						|
    parenthesesOpenPos;
 | 
						|
  var pos = 0;
 | 
						|
  var code = value.charCodeAt(pos);
 | 
						|
  var max = value.length;
 | 
						|
  var stack = [{ nodes: tokens }];
 | 
						|
  var balanced = 0;
 | 
						|
  var parent;
 | 
						|
 | 
						|
  var name = "";
 | 
						|
  var before = "";
 | 
						|
  var after = "";
 | 
						|
 | 
						|
  while (pos < max) {
 | 
						|
    // Whitespaces
 | 
						|
    if (code <= 32) {
 | 
						|
      next = pos;
 | 
						|
      do {
 | 
						|
        next += 1;
 | 
						|
        code = value.charCodeAt(next);
 | 
						|
      } while (code <= 32);
 | 
						|
      token = value.slice(pos, next);
 | 
						|
 | 
						|
      prev = tokens[tokens.length - 1];
 | 
						|
      if (code === closeParentheses && balanced) {
 | 
						|
        after = token;
 | 
						|
      } else if (prev && prev.type === "div") {
 | 
						|
        prev.after = token;
 | 
						|
        prev.sourceEndIndex += token.length;
 | 
						|
      } else if (
 | 
						|
        code === comma ||
 | 
						|
        code === colon ||
 | 
						|
        (code === slash &&
 | 
						|
          value.charCodeAt(next + 1) !== star &&
 | 
						|
          (!parent ||
 | 
						|
            (parent && parent.type === "function" && parent.value !== "calc")))
 | 
						|
      ) {
 | 
						|
        before = token;
 | 
						|
      } else {
 | 
						|
        tokens.push({
 | 
						|
          type: "space",
 | 
						|
          sourceIndex: pos,
 | 
						|
          sourceEndIndex: next,
 | 
						|
          value: token
 | 
						|
        });
 | 
						|
      }
 | 
						|
 | 
						|
      pos = next;
 | 
						|
 | 
						|
      // Quotes
 | 
						|
    } else if (code === singleQuote || code === doubleQuote) {
 | 
						|
      next = pos;
 | 
						|
      quote = code === singleQuote ? "'" : '"';
 | 
						|
      token = {
 | 
						|
        type: "string",
 | 
						|
        sourceIndex: pos,
 | 
						|
        quote: quote
 | 
						|
      };
 | 
						|
      do {
 | 
						|
        escape = false;
 | 
						|
        next = value.indexOf(quote, next + 1);
 | 
						|
        if (~next) {
 | 
						|
          escapePos = next;
 | 
						|
          while (value.charCodeAt(escapePos - 1) === backslash) {
 | 
						|
            escapePos -= 1;
 | 
						|
            escape = !escape;
 | 
						|
          }
 | 
						|
        } else {
 | 
						|
          value += quote;
 | 
						|
          next = value.length - 1;
 | 
						|
          token.unclosed = true;
 | 
						|
        }
 | 
						|
      } while (escape);
 | 
						|
      token.value = value.slice(pos + 1, next);
 | 
						|
      token.sourceEndIndex = token.unclosed ? next : next + 1;
 | 
						|
      tokens.push(token);
 | 
						|
      pos = next + 1;
 | 
						|
      code = value.charCodeAt(pos);
 | 
						|
 | 
						|
      // Comments
 | 
						|
    } else if (code === slash && value.charCodeAt(pos + 1) === star) {
 | 
						|
      next = value.indexOf("*/", pos);
 | 
						|
 | 
						|
      token = {
 | 
						|
        type: "comment",
 | 
						|
        sourceIndex: pos,
 | 
						|
        sourceEndIndex: next + 2
 | 
						|
      };
 | 
						|
 | 
						|
      if (next === -1) {
 | 
						|
        token.unclosed = true;
 | 
						|
        next = value.length;
 | 
						|
        token.sourceEndIndex = next;
 | 
						|
      }
 | 
						|
 | 
						|
      token.value = value.slice(pos + 2, next);
 | 
						|
      tokens.push(token);
 | 
						|
 | 
						|
      pos = next + 2;
 | 
						|
      code = value.charCodeAt(pos);
 | 
						|
 | 
						|
      // Operation within calc
 | 
						|
    } else if (
 | 
						|
      (code === slash || code === star) &&
 | 
						|
      parent &&
 | 
						|
      parent.type === "function" &&
 | 
						|
      parent.value === "calc"
 | 
						|
    ) {
 | 
						|
      token = value[pos];
 | 
						|
      tokens.push({
 | 
						|
        type: "word",
 | 
						|
        sourceIndex: pos - before.length,
 | 
						|
        sourceEndIndex: pos + token.length,
 | 
						|
        value: token
 | 
						|
      });
 | 
						|
      pos += 1;
 | 
						|
      code = value.charCodeAt(pos);
 | 
						|
 | 
						|
      // Dividers
 | 
						|
    } else if (code === slash || code === comma || code === colon) {
 | 
						|
      token = value[pos];
 | 
						|
 | 
						|
      tokens.push({
 | 
						|
        type: "div",
 | 
						|
        sourceIndex: pos - before.length,
 | 
						|
        sourceEndIndex: pos + token.length,
 | 
						|
        value: token,
 | 
						|
        before: before,
 | 
						|
        after: ""
 | 
						|
      });
 | 
						|
      before = "";
 | 
						|
 | 
						|
      pos += 1;
 | 
						|
      code = value.charCodeAt(pos);
 | 
						|
 | 
						|
      // Open parentheses
 | 
						|
    } else if (openParentheses === code) {
 | 
						|
      // Whitespaces after open parentheses
 | 
						|
      next = pos;
 | 
						|
      do {
 | 
						|
        next += 1;
 | 
						|
        code = value.charCodeAt(next);
 | 
						|
      } while (code <= 32);
 | 
						|
      parenthesesOpenPos = pos;
 | 
						|
      token = {
 | 
						|
        type: "function",
 | 
						|
        sourceIndex: pos - name.length,
 | 
						|
        value: name,
 | 
						|
        before: value.slice(parenthesesOpenPos + 1, next)
 | 
						|
      };
 | 
						|
      pos = next;
 | 
						|
 | 
						|
      if (name === "url" && code !== singleQuote && code !== doubleQuote) {
 | 
						|
        next -= 1;
 | 
						|
        do {
 | 
						|
          escape = false;
 | 
						|
          next = value.indexOf(")", next + 1);
 | 
						|
          if (~next) {
 | 
						|
            escapePos = next;
 | 
						|
            while (value.charCodeAt(escapePos - 1) === backslash) {
 | 
						|
              escapePos -= 1;
 | 
						|
              escape = !escape;
 | 
						|
            }
 | 
						|
          } else {
 | 
						|
            value += ")";
 | 
						|
            next = value.length - 1;
 | 
						|
            token.unclosed = true;
 | 
						|
          }
 | 
						|
        } while (escape);
 | 
						|
        // Whitespaces before closed
 | 
						|
        whitespacePos = next;
 | 
						|
        do {
 | 
						|
          whitespacePos -= 1;
 | 
						|
          code = value.charCodeAt(whitespacePos);
 | 
						|
        } while (code <= 32);
 | 
						|
        if (parenthesesOpenPos < whitespacePos) {
 | 
						|
          if (pos !== whitespacePos + 1) {
 | 
						|
            token.nodes = [
 | 
						|
              {
 | 
						|
                type: "word",
 | 
						|
                sourceIndex: pos,
 | 
						|
                sourceEndIndex: whitespacePos + 1,
 | 
						|
                value: value.slice(pos, whitespacePos + 1)
 | 
						|
              }
 | 
						|
            ];
 | 
						|
          } else {
 | 
						|
            token.nodes = [];
 | 
						|
          }
 | 
						|
          if (token.unclosed && whitespacePos + 1 !== next) {
 | 
						|
            token.after = "";
 | 
						|
            token.nodes.push({
 | 
						|
              type: "space",
 | 
						|
              sourceIndex: whitespacePos + 1,
 | 
						|
              sourceEndIndex: next,
 | 
						|
              value: value.slice(whitespacePos + 1, next)
 | 
						|
            });
 | 
						|
          } else {
 | 
						|
            token.after = value.slice(whitespacePos + 1, next);
 | 
						|
            token.sourceEndIndex = next;
 | 
						|
          }
 | 
						|
        } else {
 | 
						|
          token.after = "";
 | 
						|
          token.nodes = [];
 | 
						|
        }
 | 
						|
        pos = next + 1;
 | 
						|
        token.sourceEndIndex = token.unclosed ? next : pos;
 | 
						|
        code = value.charCodeAt(pos);
 | 
						|
        tokens.push(token);
 | 
						|
      } else {
 | 
						|
        balanced += 1;
 | 
						|
        token.after = "";
 | 
						|
        token.sourceEndIndex = pos + 1;
 | 
						|
        tokens.push(token);
 | 
						|
        stack.push(token);
 | 
						|
        tokens = token.nodes = [];
 | 
						|
        parent = token;
 | 
						|
      }
 | 
						|
      name = "";
 | 
						|
 | 
						|
      // Close parentheses
 | 
						|
    } else if (closeParentheses === code && balanced) {
 | 
						|
      pos += 1;
 | 
						|
      code = value.charCodeAt(pos);
 | 
						|
 | 
						|
      parent.after = after;
 | 
						|
      parent.sourceEndIndex += after.length;
 | 
						|
      after = "";
 | 
						|
      balanced -= 1;
 | 
						|
      stack[stack.length - 1].sourceEndIndex = pos;
 | 
						|
      stack.pop();
 | 
						|
      parent = stack[balanced];
 | 
						|
      tokens = parent.nodes;
 | 
						|
 | 
						|
      // Words
 | 
						|
    } else {
 | 
						|
      next = pos;
 | 
						|
      do {
 | 
						|
        if (code === backslash) {
 | 
						|
          next += 1;
 | 
						|
        }
 | 
						|
        next += 1;
 | 
						|
        code = value.charCodeAt(next);
 | 
						|
      } while (
 | 
						|
        next < max &&
 | 
						|
        !(
 | 
						|
          code <= 32 ||
 | 
						|
          code === singleQuote ||
 | 
						|
          code === doubleQuote ||
 | 
						|
          code === comma ||
 | 
						|
          code === colon ||
 | 
						|
          code === slash ||
 | 
						|
          code === openParentheses ||
 | 
						|
          (code === star &&
 | 
						|
            parent &&
 | 
						|
            parent.type === "function" &&
 | 
						|
            parent.value === "calc") ||
 | 
						|
          (code === slash &&
 | 
						|
            parent.type === "function" &&
 | 
						|
            parent.value === "calc") ||
 | 
						|
          (code === closeParentheses && balanced)
 | 
						|
        )
 | 
						|
      );
 | 
						|
      token = value.slice(pos, next);
 | 
						|
 | 
						|
      if (openParentheses === code) {
 | 
						|
        name = token;
 | 
						|
      } else if (
 | 
						|
        (uLower === token.charCodeAt(0) || uUpper === token.charCodeAt(0)) &&
 | 
						|
        plus === token.charCodeAt(1) &&
 | 
						|
        isUnicodeRange.test(token.slice(2))
 | 
						|
      ) {
 | 
						|
        tokens.push({
 | 
						|
          type: "unicode-range",
 | 
						|
          sourceIndex: pos,
 | 
						|
          sourceEndIndex: next,
 | 
						|
          value: token
 | 
						|
        });
 | 
						|
      } else {
 | 
						|
        tokens.push({
 | 
						|
          type: "word",
 | 
						|
          sourceIndex: pos,
 | 
						|
          sourceEndIndex: next,
 | 
						|
          value: token
 | 
						|
        });
 | 
						|
      }
 | 
						|
 | 
						|
      pos = next;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  for (pos = stack.length - 1; pos; pos -= 1) {
 | 
						|
    stack[pos].unclosed = true;
 | 
						|
    stack[pos].sourceEndIndex = value.length;
 | 
						|
  }
 | 
						|
 | 
						|
  return stack[0].nodes;
 | 
						|
};
 |