From 3f45a44fe0a495089033102ae9c5775b9f478665 Mon Sep 17 00:00:00 2001 From: Zivsteve Date: Sat, 8 May 2021 17:44:23 -0400 Subject: [PATCH] chore: cleanup functional notations --- src/functional-notations/env.ts | 6 +++ src/functional-notations/math.ts | 16 +++++++ src/utils/functional-notation.ts | 57 ++++++++++++++++++++++++ src/utils/valid-pseudo-class.ts | 6 +-- src/utils/valid-styles.ts | 2 +- src/utils/values.ts | 76 +++----------------------------- 6 files changed, 90 insertions(+), 73 deletions(-) create mode 100644 src/functional-notations/env.ts create mode 100644 src/functional-notations/math.ts create mode 100644 src/utils/functional-notation.ts diff --git a/src/functional-notations/env.ts b/src/functional-notations/env.ts new file mode 100644 index 0000000..1d7dbb1 --- /dev/null +++ b/src/functional-notations/env.ts @@ -0,0 +1,6 @@ +import Base from '../base'; + +export function envFn(v: string, fallback?: string | number) { + const envVal = Base.envVariables[v.toLowerCase()]; + return (typeof envVal === 'function' ? envVal() : envVal) ?? fallback; +} diff --git a/src/functional-notations/math.ts b/src/functional-notations/math.ts new file mode 100644 index 0000000..867b205 --- /dev/null +++ b/src/functional-notations/math.ts @@ -0,0 +1,16 @@ +import { toLength } from '../utils/values'; + +export const minFn = Math.min; +export const maxFn = Math.max; + +export function calcFn(equation = '') { + const splitValues = equation.split(/([+\-*/])/g); + const parsedEquation = splitValues.map((v) => v.trim()).map((v) => (v.match(/[+\-*/]/) ? v : toLength(v) || v)); + + // eslint-disable-next-line no-eval + return eval(parsedEquation.join('')); +} + +export function clampFn(val: number, min = 0, max = Number.MAX_VALUE) { + return Math.min(Math.max(val, min), max); +} diff --git a/src/utils/functional-notation.ts b/src/utils/functional-notation.ts new file mode 100644 index 0000000..bfe016d --- /dev/null +++ b/src/utils/functional-notation.ts @@ -0,0 +1,57 @@ +import { envFn } from '../functional-notations/env'; +import { minFn, maxFn, calcFn, clampFn } from '../functional-notations/math'; +import { toLength } from './values'; + +type FunctionalNotation = (...args: any) => string | number | undefined; + +const functions: { [key: string]: FunctionalNotation } = { + min: minFn, + max: maxFn, + calc: (e: string) => calcFn(e), + clamp: (min: number, val: number, max: number) => clampFn(val, min, max), + env: (v: string, o_fallback?: string | number) => envFn(v, o_fallback), +}; + +export function functionalNotation(value: string) { + if (typeof value === 'string') { + const funcName = value.split('(')[0]; + if (Object.keys(functions).includes(funcName)) { + const values = value.substring(value.indexOf('(') + 1, value.lastIndexOf(')')); + + if (!values) { + return null; + } + + const isArgs = values?.includes(','); + const valueArgs = + values + ?.replace(/,\s*(?=[^)^)]*(?:\(|\(|$))/g, '##') + ?.split('##') + ?.map((v) => toLength(v.trim())) || []; + + const func = functions?.[funcName as keyof typeof functions]; + const args = getFunctionParams(func).filter((p) => !p.startsWith('o_')); + + if (args.length > 0 && args.length !== valueArgs.length) { + console.log(func.prototype); + console.error( + `Function "${funcName}" is missing arguments. Expected ${func.length} but received ${valueArgs.length}`, + ); + return null; + } + + return isArgs ? func?.(...valueArgs) : func?.(values); + } + } + return value; +} + +const STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/gm; +const ARGUMENT_NAMES = /([^\s,]+)/g; + +function getFunctionParams(func: Function) { + var fnStr = func.toString().replace(STRIP_COMMENTS, ''); + var result = fnStr.slice(fnStr.indexOf('(') + 1, fnStr.indexOf(')')).match(ARGUMENT_NAMES); + if (result === null) result = []; + return result; +} diff --git a/src/utils/valid-pseudo-class.ts b/src/utils/valid-pseudo-class.ts index 204f43a..b78ba95 100644 --- a/src/utils/valid-pseudo-class.ts +++ b/src/utils/valid-pseudo-class.ts @@ -1,5 +1,5 @@ import type { NativeComponents, AnimatableComponentProps } from '../Animated'; -import { calc } from './values'; +import { calcFn } from '../functional-notations/math'; type StateCallbackType = Readonly<{ focused: boolean; @@ -62,7 +62,7 @@ export function isValidPseudoClass( .replace(/^n/g, '1') .replace(/n/g, '*1'); const b = +(vals[1] || 0); - const num = (index + 1 - b) / calc(m); + const num = (index + 1 - b) / calcFn(m); return Number.isInteger(num) && num >= 0; } return index === +value - 1; @@ -83,7 +83,7 @@ export function isValidPseudoClass( .replace(/^n/g, '1') .replace(/n/g, '*1'); const b = +(vals[1] || 0); - const num = (rIndex + 1 - b) / calc(m); + const num = (rIndex + 1 - b) / calcFn(m); return Number.isInteger(num) && num >= 0; } return rIndex === +value - 1; diff --git a/src/utils/valid-styles.ts b/src/utils/valid-styles.ts index f8ac1d2..8c1a8b1 100644 --- a/src/utils/valid-styles.ts +++ b/src/utils/valid-styles.ts @@ -1,4 +1,4 @@ -import type { Styles } from 'src/types'; +import type { Styles } from '../types'; export const validStyles = [ 'display', diff --git a/src/utils/values.ts b/src/utils/values.ts index eb0d1a1..bcf222b 100644 --- a/src/utils/values.ts +++ b/src/utils/values.ts @@ -1,8 +1,7 @@ import { Dimensions } from 'react-native'; -import { numeralPreprocessor } from '../preprocessors/numeral'; import type { EasingNameCamel, EasingNameDashed } from '../types'; import { Easing } from '..'; -import Base from '../base'; +import { functionalNotation } from './functional-notation'; const RE_LENGTH_UNIT = /(cm|mm|Q|in|pt|pc|px|em|ex|ch|rem|lh|vw|vh|vmin|vmax)?\s*$/; const RE_RESOLUTION_UNIT = /(dpi|dpcm|dppx)?\s*$/; @@ -10,11 +9,11 @@ const RE_RESOLUTION_UNIT = /(dpi|dpcm|dppx)?\s*$/; export function toDecimal(ratio: number | string, defaultRaw = false) { let decimal = +ratio; - if (!decimal) { + if (!isNaN(decimal)) { const numbers = `${ratio}`.match(/^(\d+)\s*\/\s*(\d+)$/) || []; decimal = +numbers[1] / +numbers[2]; + return decimal; } - return defaultRaw ? ratio : decimal; } @@ -85,10 +84,11 @@ export function isLength(value: string | number) { return LENGTH.test(`${value}`) || ZERO.test(`${value}`); } -export function toLength(value: string | number) { - const parsedValue = toPx(toDecimal(functionalNotation(`${value}`) || value, true), true); +export function toLength(value: string | number): string | number { + const fnValue = functionalNotation(`${value}`); + const parsedValue = toPx(toDecimal(`${fnValue || value}`, true), true); const rawValue = (`${parsedValue}`.endsWith('%') ? parsedValue : parseFloat(`${parsedValue}`)) || undefined; - return rawValue; + return rawValue ?? fnValue ?? value; } export function toDuration(duration?: number | string) { @@ -123,65 +123,3 @@ export function toCamelCase(str: string) { export function deepEquals(a: object, b: object) { return a === b || JSON.stringify(a) === JSON.stringify(b); } - -export function calc(equation = '') { - const splitValues = equation.split(/([+\-*/])/g); - const parsedEquation = splitValues - .map((v) => v.trim()) - .map((v) => (v.match(/[+\-*/]/) ? v : numeralPreprocessor('x', v)?.x || v)); - - // eslint-disable-next-line no-eval - return eval(parsedEquation.join('')); -} - -export function clamp(val: number, min = 0, max = Number.MAX_VALUE) { - return Math.min(Math.max(val, min), max); -} - -export function env(v: string, fallback: string | number) { - return Base.envVariables[v.toLowerCase()] || fallback; -} - -// -type FunctionalNotation = (...args: any) => string | number; - -const functions: { [key: string]: FunctionalNotation } = { - calc: (e: string) => calc(e), - min: Math.min, - max: Math.max, - clamp: (min: number, val: number, max: number) => clamp(val, min, max), - env: (v: string, fallback: string | number) => env(v, fallback), -}; - -export function functionalNotation(value: string) { - if (typeof value === 'string') { - const funcName = value.split('(')[0]; - if (Object.keys(functions).includes(funcName)) { - const values = value.substring(value.indexOf('(') + 1, value.lastIndexOf(')')); - - if (!values) { - return null; - } - - const isArgs = values?.includes(','); - const valueArgs = - values - ?.replace(/,\s*(?=[^)^)]*(?:\(|\(|$))/g, '##') - ?.split('##') - ?.map((v) => v.trim()) - ?.map((v) => numeralPreprocessor('x', v)?.x || v) || []; - - const func = functions?.[funcName as keyof typeof functions]; - - if (func.length > 0 && func.length !== valueArgs.length) { - console.error( - `Function "${funcName}" is missing arguments. Expected ${func.length} but received ${valueArgs.length}`, - ); - return null; - } - - return (isArgs ? func?.(...valueArgs) : func?.(values)) || value; - } - } - return value; -}