Spaces:
Running
Running
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 | |
} | |