Skip to content

Commit

Permalink
chore: add minimal calc parser
Browse files Browse the repository at this point in the history
  • Loading branch information
nmn committed Mar 3, 2025
1 parent efd89b2 commit 56c7fe9
Showing 1 changed file with 181 additions and 0 deletions.
181 changes: 181 additions & 0 deletions packages/style-value-parser/src/css-types-from-tokens/calc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
*/

import { type CalcConstant, calcConstant } from './calc-constant';
import { Percentage } from './common-types';
// import { type Dimension, dimension } from './dimension';

import { TokenParser } from '../core2';
import type { TokenDimension } from '@csstools/css-tokenizer';

type Addition = {
type: '+',
left: CalcValue,
right: CalcValue,
};
type Subtraction = {
type: '-',
left: CalcValue,
right: CalcValue,
};
type Multiplication = {
type: '*',
left: CalcValue,
right: CalcValue,
};
type Division = {
type: '/',
left: CalcValue,
right: CalcValue,
};

type CalcValue =
| number
| TokenDimension[4]
| Percentage
| CalcConstant
| Addition
| Subtraction
| Multiplication
| Division;

const valueParser = TokenParser.oneOf(
TokenParser.tokens.Number.map((number) => number[4].value),
TokenParser.tokens.Dimension.map((dimension) => dimension[4]),
Percentage.parser,
calcConstant,
);

const composeAddAndSubtraction = (
valuesAndOperators: $ReadOnlyArray<CalcValue | string>,
): CalcValue => {
if (valuesAndOperators.length === 1) {
if (typeof valuesAndOperators[0] === 'string') {
throw new Error('Invalid operator');
}
return valuesAndOperators[0];
}
const firstOperator = valuesAndOperators.findIndex(
(op) => op === '+' || op === '-',
);
if (firstOperator === -1) {
throw new Error('No valid operator found');
}
const left = valuesAndOperators.slice(0, firstOperator);
const right = valuesAndOperators.slice(firstOperator + 1);

if (valuesAndOperators[firstOperator] === '+') {
return {
type: '+',
left: composeAddAndSubtraction(left),
right: composeAddAndSubtraction(right),
};
}
return {
type: '-',
left: composeAddAndSubtraction(left),
right: composeAddAndSubtraction(right),
};
};

const splitByMultiplicationOrDivision = (
valuesAndOperators: $ReadOnlyArray<CalcValue | string>,
): CalcValue => {
if (valuesAndOperators.length === 1) {
if (typeof valuesAndOperators[0] === 'string') {
throw new Error('Invalid operator');
}
return valuesAndOperators[0];
}
const firstOperator = valuesAndOperators.findIndex(
(op) => op === '*' || op === '/',
);
if (firstOperator === -1) {
return composeAddAndSubtraction(valuesAndOperators);
}
const left = valuesAndOperators.slice(0, firstOperator);
const right = valuesAndOperators.slice(firstOperator + 1);

if (valuesAndOperators[firstOperator] === '*') {
return {
type: '*',
left: composeAddAndSubtraction(left),
right: splitByMultiplicationOrDivision(right),
};
}

return {
type: '/',
left: composeAddAndSubtraction(left),
right: splitByMultiplicationOrDivision(right),
};
};

const operationsParser: TokenParser<CalcValue> = TokenParser.sequence(
TokenParser.oneOf(valueParser, () =>
TokenParser.sequence(
TokenParser.tokens.OpenParen,
operationsParser,
TokenParser.tokens.CloseParen,
)
.surroundedBy(TokenParser.tokens.Whitespace.optional)
.map(([_, value]) => value),
),
TokenParser.sequence(
TokenParser.tokens.Delim.map((delim) => delim[4].value).where(
(delim) =>
delim === '*' || delim === '/' || delim === '+' || delim === '-',
),
TokenParser.oneOrMore(
TokenParser.oneOf(valueParser, () =>
TokenParser.sequence(
TokenParser.tokens.OpenParen,
operationsParser,
TokenParser.tokens.CloseParen,
)
.surroundedBy(TokenParser.tokens.Whitespace.optional)
.map(([_, value]) => value),
),
),
).separatedBy(TokenParser.tokens.Whitespace).optional,
)
.separatedBy(TokenParser.tokens.Whitespace)
.map(([firstValue, restOfTheValues]) => {
if (restOfTheValues == null || restOfTheValues.length === 0) {
return firstValue;
}

const valuesAndOperators: $ReadOnlyArray<CalcValue | string> = [
firstValue,
...restOfTheValues.flat(),
];

return splitByMultiplicationOrDivision(valuesAndOperators);
});

export class Calc {
+value: CalcValue;
constructor(value: this['value']) {
this.value = value;
}
toString(): string {
return this.value.toString();
}
static get parser(): TokenParser<Calc> {
return TokenParser.sequence(
TokenParser.tokens.Function.map((func) => func[4].value).where(
(func) => func === 'calc',
),
TokenParser.oneOf(operationsParser, valueParser),
TokenParser.tokens.CloseParen,
)
.surroundedBy(TokenParser.tokens.Whitespace.optional)
.map(([_, value, _closeParen]) => new Calc(value));
}
}

0 comments on commit 56c7fe9

Please sign in to comment.