304 lines
7.8 KiB
Plaintext
304 lines
7.8 KiB
Plaintext
|
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
|
||
|
}
|