const LOWER_A = 0x61
const LOWER_Z = 0x7a
const UPPER_A = 0x41
const UPPER_Z = 0x5a
const LOWER_E = 0x65
const UPPER_E = 0x45
const ZERO = 0x30
const NINE = 0x39
const ADD = 0x2b
const SUB = 0x2d
const MUL = 0x2a
const DIV = 0x2f
const OPEN_PAREN = 0x28
const CLOSE_PAREN = 0x29
const COMMA = 0x2c
const SPACE = 0x20
const PERCENT = 0x25
const MATH_FUNCTIONS = [
'calc',
'min',
'max',
'clamp',
'mod',
'rem',
'sin',
'cos',
'tan',
'asin',
'acos',
'atan',
'atan2',
'pow',
'sqrt',
'hypot',
'log',
'exp',
'round',
]
export function hasMathFn(input: string) {
return input.indexOf('(') !== -1 && MATH_FUNCTIONS.some((fn) => input.includes(`${fn}(`))
}
export function addWhitespaceAroundMathOperators(input: string) {
if (!MATH_FUNCTIONS.some((fn) => input.includes(fn))) {
return input
}
let result = ''
let formattable: boolean[] = []
let valuePos = null
let lastValuePos = null
for (let i = 0; i < input.length; i++) {
let char = input.charCodeAt(i)
if (char >= ZERO && char <= NINE) {
valuePos = i
}
else if (
valuePos !== null &&
(char === PERCENT ||
(char >= LOWER_A && char <= LOWER_Z) ||
(char >= UPPER_A && char <= UPPER_Z))
) {
valuePos = i
}
else {
lastValuePos = valuePos
valuePos = null
}
if (char === OPEN_PAREN) {
result += input[i]
let start = i
for (let j = i - 1; j >= 0; j--) {
let inner = input.charCodeAt(j)
if (inner >= ZERO && inner <= NINE) {
start = j
} else if (inner >= LOWER_A && inner <= LOWER_Z) {
start = j
} else {
break
}
}
let fn = input.slice(start, i)
if (MATH_FUNCTIONS.includes(fn)) {
formattable.unshift(true)
continue
}
else if (formattable[0] && fn === '') {
formattable.unshift(true)
continue
}
formattable.unshift(false)
continue
}
else if (char === CLOSE_PAREN) {
result += input[i]
formattable.shift()
}
else if (char === COMMA && formattable[0]) {
result += `, `
continue
}
else if (char === SPACE && formattable[0] && result.charCodeAt(result.length - 1) === SPACE) {
continue
}
else if ((char === ADD || char === MUL || char === DIV || char === SUB) && formattable[0]) {
let trimmed = result.trimEnd()
let prev = trimmed.charCodeAt(trimmed.length - 1)
let prevPrev = trimmed.charCodeAt(trimmed.length - 2)
let next = input.charCodeAt(i + 1)
if ((prev === LOWER_E || prev === UPPER_E) && prevPrev >= ZERO && prevPrev <= NINE) {
result += input[i]
continue
}
else if (prev === ADD || prev === MUL || prev === DIV || prev === SUB) {
result += input[i]
continue
}
else if (prev === OPEN_PAREN || prev === COMMA) {
result += input[i]
continue
}
else if (input.charCodeAt(i - 1) === SPACE) {
result += `${input[i]} `
}
else if (
(prev >= ZERO && prev <= NINE) ||
(next >= ZERO && next <= NINE) ||
prev === CLOSE_PAREN ||
next === OPEN_PAREN ||
next === ADD ||
next === MUL ||
next === DIV ||
next === SUB ||
(lastValuePos !== null && lastValuePos === i - 1)
) {
result += ` ${input[i]} `
}
else {
result += input[i]
}
}
else {
result += input[i]
}
}
return result
}