import { dimensions } from './utils/dimensions'
import { isLength } from './utils/infer-data-type'
import * as ValueParser from './value-parser'
import { walk, WalkAction } from './walk'
export function constantFoldDeclaration(input: string, rem: number | null = null): string {
let folded = false
let valueAst = ValueParser.parse(input)
walk(valueAst, {
exit(valueNode) {
if (
valueNode.kind === 'word' &&
valueNode.value !== '0'
) {
let canonical = canonicalizeDimension(valueNode.value, rem)
if (canonical === null) return
if (canonical === valueNode.value) return
folded = true
return WalkAction.ReplaceSkip(ValueParser.word(canonical))
}
else if (
valueNode.kind === 'function' &&
(valueNode.value === 'calc' || valueNode.value === '')
) {
if (valueNode.nodes.length !== 5) return
let lhs = dimensions.get(valueNode.nodes[0].value)
let operator = valueNode.nodes[2].value
let rhs = dimensions.get(valueNode.nodes[4].value)
if (
operator === '*' &&
((lhs?.[0] === 0 && lhs?.[1] === null) ||
(rhs?.[0] === 0 && rhs?.[1] === null))
) {
folded = true
return WalkAction.ReplaceSkip(ValueParser.word('0'))
}
if (lhs === null || rhs === null) {
return
}
switch (operator) {
case '*': {
if (
lhs[1] === rhs[1] ||
(lhs[1] === null && rhs[1] !== null) ||
(lhs[1] !== null && rhs[1] === null)
) {
folded = true
return WalkAction.ReplaceSkip(ValueParser.word(`${lhs[0] * rhs[0]}${lhs[1] ?? ''}`))
}
break
}
case '+': {
if (
lhs[1] === rhs[1]
) {
folded = true
return WalkAction.ReplaceSkip(ValueParser.word(`${lhs[0] + rhs[0]}${lhs[1] ?? ''}`))
}
break
}
case '-': {
if (
lhs[1] === rhs[1]
) {
folded = true
return WalkAction.ReplaceSkip(ValueParser.word(`${lhs[0] - rhs[0]}${lhs[1] ?? ''}`))
}
break
}
case '/': {
if (
rhs[0] !== 0 &&
((lhs[1] === null && rhs[1] === null) ||
(lhs[1] !== null && rhs[1] === null))
) {
folded = true
return WalkAction.ReplaceSkip(ValueParser.word(`${lhs[0] / rhs[0]}${lhs[1] ?? ''}`))
}
break
}
}
}
},
})
return folded ? ValueParser.toCss(valueAst) : input
}
function canonicalizeDimension(input: string, rem: number | null = null): string | null {
let dimension = dimensions.get(input)
if (dimension === null) return null
let [value, unit] = dimension
if (unit === null) return `${value}`
if (value === 0 && isLength(input)) return '0'
switch (unit.toLowerCase()) {
case 'in': return `${value * 96}px`
case 'cm': return `${value * 96 / 2.54}px` // 1cm = 37.795px
case 'mm': return `${value * 96 / 2.54 / 10}px` // 1mm = 3.779px
case 'q': return `${value * 96 / 2.54 / 10 / 4}px`
case 'pc': return `${value * 96 / 6}px` // 1pc = 16.000px
case 'pt': return `${value * 96 / 72}px`
case 'rem': return rem !== null ? `${value * rem}px` : null
case 'grad': return `${value * 0.9}deg`
case 'rad': return `${value * 180 / Math.PI}deg` // 1rad = 57.296deg
case 'turn': return `${value * 360}deg` // 1turn = 360.000deg
// <time> to s, https://developer.mozilla.org/en-US/docs/Web/CSS/time
case 'ms': return `${value / 1000}s` // 1ms = 0.001s
// <frequency> to hz, https://developer.mozilla.org/en-US/docs/Web/CSS/frequency
case 'khz': return `${value * 1000}hz` // 1kHz = 1000Hz
default: return `${value}${unit}` // No canonicalization possible, return as-is
}
}