Skip to content

Commit

Permalink
chore: cleanup functional notations
Browse files Browse the repository at this point in the history
  • Loading branch information
Zivsteve committed May 8, 2021
1 parent 0a09ecd commit 3f45a44
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 73 deletions.
6 changes: 6 additions & 0 deletions src/functional-notations/env.ts
Original file line number Diff line number Diff line change
@@ -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;
}
16 changes: 16 additions & 0 deletions src/functional-notations/math.ts
Original file line number Diff line number Diff line change
@@ -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);
}
57 changes: 57 additions & 0 deletions src/utils/functional-notation.ts
Original file line number Diff line number Diff line change
@@ -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;
}
6 changes: 3 additions & 3 deletions src/utils/valid-pseudo-class.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/utils/valid-styles.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Styles } from 'src/types';
import type { Styles } from '../types';

export const validStyles = [
'display',
Expand Down
76 changes: 7 additions & 69 deletions src/utils/values.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
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*$/;

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;
}

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
}

0 comments on commit 3f45a44

Please sign in to comment.