| 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; | 
| }; |