| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251 | 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);module.exports = function(input) {  var tokens = [];  var value = input;  var next, quote, prev, token, escape, escapePos, whitespacePos;  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;      } else if (        code === comma ||        code === colon ||        (code === slash && value.charCodeAt(next + 1) !== star)      ) {        before = token;      } else {        tokens.push({          type: "space",          sourceIndex: pos,          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);      tokens.push(token);      pos = next + 1;      code = value.charCodeAt(pos);      // Comments    } else if (code === slash && value.charCodeAt(pos + 1) === star) {      token = {        type: "comment",        sourceIndex: pos      };      next = value.indexOf("*/", pos);      if (next === -1) {        token.unclosed = true;        next = value.length;      }      token.value = value.slice(pos + 2, next);      tokens.push(token);      pos = next + 2;      code = value.charCodeAt(pos);      // Dividers    } else if (code === slash || code === comma || code === colon) {      token = value[pos];      tokens.push({        type: "div",        sourceIndex: pos - before.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);      token = {        type: "function",        sourceIndex: pos - name.length,        value: name,        before: value.slice(pos + 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 (pos !== whitespacePos + 1) {          token.nodes = [            {              type: "word",              sourceIndex: pos,              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,            value: value.slice(whitespacePos + 1, next)          });        } else {          token.after = value.slice(whitespacePos + 1, next);        }        pos = next + 1;        code = value.charCodeAt(pos);        tokens.push(token);      } else {        balanced += 1;        token.after = "";        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;      after = "";      balanced -= 1;      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 === closeParentheses && balanced)        )      );      token = value.slice(pos, next);      if (openParentheses === code) {        name = token;      } else {        tokens.push({          type: "word",          sourceIndex: pos,          value: token        });      }      pos = next;    }  }  for (pos = stack.length - 1; pos; pos -= 1) {    stack[pos].unclosed = true;  }  return stack[0].nodes;};
 |