aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAkshay Nair <phenax5@gmail.com>2024-01-21 16:12:43 +0530
committerAkshay Nair <phenax5@gmail.com>2024-01-21 16:12:43 +0530
commitc7a137861494edd65d0c8de76ab09f422ab15481 (patch)
tree0aaed5755228ed38cde3ac63dfd8701e7d7442a1
parentf7a4596469d0652f868cad2d97a9edf92f4bc4c7 (diff)
downloadcss-everything-c7a137861494edd65d0c8de76ab09f422ab15481.tar.gz
css-everything-c7a137861494edd65d0c8de76ab09f422ab15481.zip
feat(eval): adds evaluation for simple binop expressions
Diffstat (limited to '')
-rw-r--r--src/eval.ts61
-rw-r--r--src/parser.ts1
-rw-r--r--tests/calc.spec.ts46
-rw-r--r--tests/parser.spec.ts18
4 files changed, 122 insertions, 4 deletions
diff --git a/src/eval.ts b/src/eval.ts
index 636023b..87bcd6d 100644
--- a/src/eval.ts
+++ b/src/eval.ts
@@ -1,4 +1,4 @@
-import { CSSUnit, Expr, parse } from './parser'
+import { CSSUnit, Expr, parse, parseExpr } from './parser'
import { Enum, constructors, match, matchString } from './utils/adt'
export interface EvalActions {
@@ -58,8 +58,10 @@ export const EvalValue = constructors<EvalValue>()
export const evalExprAsString = async (
expr: Expr,
actions: EvalActions,
-): Promise<string | undefined> =>
- evalValueToString(await evalExpr(expr, actions))
+): Promise<string | undefined> => {
+ const evalVal = await evalExpr(expr, actions)
+ return evalValueToString(evalVal)
+}
export const evalExpr = async (
expr: Expr,
@@ -72,11 +74,15 @@ export const evalExpr = async (
EvalValue.Number(
matchString<number, CSSUnit>(unit, {
s: () => value * 1000,
+ rem: () => value * 16, // TODO: get root font size
+ em: () => value * 16, // TODO: get parent font size
+ '%': () => value * 100, // TODO: Get parent width
_: () => value,
}),
),
Identifier: async s => EvalValue.String(s),
VarIdentifier: async s => EvalValue.VarIdentifier(s),
+ Parens: ({ expr }) => evalExpr(expr, actions),
_: async _ => EvalValue.Void(),
})
}
@@ -119,7 +125,9 @@ const getFunctions = (
): Promise<EvalValue> => {
const getVariable = async () => {
const varName = await evalExpr(args[0], actions)
- const defaultValue = args[1] && (await evalExpr(args[1], actions))
+ const defaultValue = args[1]
+ ? await evalExpr(args[1], actions)
+ : EvalValue.Void()
return match<Promise<EvalValue>, EvalValue>(varName, {
VarIdentifier: async name => {
@@ -358,10 +366,55 @@ const getFunctions = (
})
},
+ calc: async () => {
+ const result = await evalCalcExpr(args[0], actions)
+ return EvalValue.Number(result)
+ },
+
_: () => Promise.reject(new Error(`Not implemented: ${name}`)),
})
}
+const evalBinOp = async (
+ left: Expr,
+ right: Expr,
+ actions: EvalActions,
+ op: (a: number, b: number) => number,
+): Promise<number> =>
+ op(await evalCalcExpr(left, actions), await evalCalcExpr(right, actions))
+
+export const evalCalcExpr = (
+ expr: Expr,
+ actions: EvalActions,
+): Promise<number> =>
+ match(expr, {
+ BinOp: async ({ op, left, right }) =>
+ matchString(op, {
+ '+': () => evalBinOp(left, right, actions, (a, b) => a + b),
+ '*': () => evalBinOp(left, right, actions, (a, b) => a * b),
+ '-': () => evalBinOp(left, right, actions, (a, b) => a - b),
+ '/': () => evalBinOp(left, right, actions, (a, b) => a / b),
+ _: () =>
+ Promise.reject(
+ new Error(`Invalid operator in calc expression: ${op}`),
+ ),
+ }),
+ Parens: ({ expr }) => evalCalcExpr(expr, actions),
+ _: async () => {
+ if (expr.tag === 'Call' && expr.value.name === 'var') {
+ const value = await evalExprAsString(expr, actions)
+ try {
+ const pvalue = await evalExpr(parseExpr(value ?? ''), actions)
+ return evalValueToNumber(pvalue) ?? 0
+ } catch (e) {
+ return 0
+ }
+ }
+ const value = await evalExpr(expr, actions)
+ return evalValueToNumber(value) ?? 0
+ },
+ })
+
export const evalArgs = (
args: Array<Expr>,
count: number,
diff --git a/src/parser.ts b/src/parser.ts
index a2bb577..c2aa30f 100644
--- a/src/parser.ts
+++ b/src/parser.ts
@@ -2,6 +2,7 @@ import { Enum, constructors, match, matchString } from './utils/adt'
import * as P from './utils/parser-comb'
import { Result } from './utils/result'
+// TODO: vh, vw
export type CSSUnit = '' | 's' | 'ms' | 'px' | '%' | 'rem' | 'em'
export type BinOp = '+' | '-' | '*' | '/'
diff --git a/tests/calc.spec.ts b/tests/calc.spec.ts
new file mode 100644
index 0000000..a4e11cd
--- /dev/null
+++ b/tests/calc.spec.ts
@@ -0,0 +1,46 @@
+import { EvalActions, EvalValue, evalExpr } from '../src/eval'
+import { parseExpr } from '../src/parser'
+import { matchString } from '../src/utils/adt'
+
+describe('calc', () => {
+ const variables = (name: string) =>
+ matchString(name, {
+ '--test-8rem': () => '8rem',
+ _: () => {},
+ })
+ const actions: EvalActions = {
+ addClass: jest.fn(),
+ removeClass: jest.fn(),
+ delay: jest.fn(),
+ jsEval: jest.fn(),
+ loadCssx: jest.fn(),
+ getVariable: jest.fn(variables),
+ updateVariable: jest.fn(),
+ setAttribute: jest.fn(),
+ getAttribute: jest.fn(),
+ withEvent: jest.fn(),
+ getFormData: jest.fn(),
+ sendRequest: jest.fn(),
+ addChildren: jest.fn(),
+ removeElement: jest.fn(),
+ callMethod: jest.fn(),
+ evaluateInScope: jest.fn(),
+ }
+
+ describe.each([
+ ['calc(8rem)', EvalValue.Number(128)],
+ ['calc(5 + 8)', EvalValue.Number(13)],
+ ['calc(5 * 8 + 1)', EvalValue.Number(41)],
+ ['calc(5 * (8 + 1))', EvalValue.Number(45)],
+ ['calc(5px * (8rem + 1))', EvalValue.Number(645)],
+ ['calc(5px * 8rem/2 + 1)', EvalValue.Number(321)],
+ ['calc(var(--test-8rem))', EvalValue.Number(128)],
+ ['calc(var(--test-1))', EvalValue.Number(0)], // Var not found
+ ['calc(5px * var(--test-8rem)/2 + 1)', EvalValue.Number(321)],
+ ])('when given "%s"', (expr, expected) => {
+ it('should evaluate the result of math', async () => {
+ const evalValue = await evalExpr(parseExpr(expr), actions)
+ expect(evalValue).toEqual(expected)
+ })
+ })
+})
diff --git a/tests/parser.spec.ts b/tests/parser.spec.ts
index 494bb51..16480e6 100644
--- a/tests/parser.spec.ts
+++ b/tests/parser.spec.ts
@@ -267,5 +267,23 @@ describe('parser', () => {
}),
])
})
+
+ it('parses calc expression with vars', () => {
+ expect(parse(`calc(5px * var(--value))`)).toEqual([
+ Expr.Call({
+ name: 'calc',
+ args: [
+ Expr.BinOp({
+ op: '*',
+ left: Expr.LiteralNumber({ value: 5, unit: 'px' }),
+ right: Expr.Call({
+ name: 'var',
+ args: [Expr.VarIdentifier('--value')],
+ }),
+ }),
+ ],
+ }),
+ ])
+ })
})
})