 c177363a19
			
		
	
	c177363a19
	
	
	
		
			
			🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			260 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			260 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| "use strict";
 | |
| 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" && false)) {
 | |
|                 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" && true) {
 | |
|             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" && true || code === slash && parent.type === "function" && true || 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;
 | |
| };
 |