Skip to content

Instantly share code, notes, and snippets.

@munrocket
Last active May 31, 2025 04:02
Show Gist options
  • Save munrocket/28aabef7de171668f927cc2eda1ca667 to your computer and use it in GitHub Desktop.
Save munrocket/28aabef7de171668f927cc2eda1ca667 to your computer and use it in GitHub Desktop.

Revisions

  1. munrocket revised this gist May 31, 2025. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion mini-math.js
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,5 @@
    /**
    * Safely evaluates a mathematical expression using `eval`.
    * Safely evaluates a mathematical expression using `Function`.
    * This function is designed to support many GLSL/WGSL functions.
    * It handles float/int expressions and basic boolean operations.
    * Safety is ensured by removing all numbers and operators from
  2. munrocket revised this gist May 30, 2025. 1 changed file with 7 additions and 5 deletions.
    12 changes: 7 additions & 5 deletions mini-math.js
    Original file line number Diff line number Diff line change
    @@ -1,8 +1,10 @@
    /**
    * Calculate a mathematical expression using eval safely.
    * It is created for support a lot of glsl/wgsl functions.
    * It support float/int expressions and minimal bool functions.
    */
    /**
    * Safely evaluates a mathematical expression using `eval`.
    * This function is designed to support many GLSL/WGSL functions.
    * It handles float/int expressions and basic boolean operations.
    * Safety is ensured by removing all numbers and operators from
    * the expression and checking the remaining symbols.
    */
    function calcMathExpression(expression) {
    const VALID_EXPRESSIONS = /^[+\-*/%(,)]*$/;
    const BOOL_FUNCTIONS = /(\^|&&|\|\||<<|>>)/g;
  3. munrocket created this gist May 30, 2025.
    89 changes: 89 additions & 0 deletions mini-math.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,89 @@
    /**
    * Calculate a mathematical expression using eval safely.
    * It is created for support a lot of glsl/wgsl functions.
    * It support float/int expressions and minimal bool functions.
    */
    function calcMathExpression(expression) {
    const VALID_EXPRESSIONS = /^[+\-*/%(,)]*$/;
    const BOOL_FUNCTIONS = /(\^|&&|\|\||<<|>>)/g;
    const INTEGER_FUNCTIONS = /(select|abs|min|max|sign)/g;
    const FLOAT_FUNCTIONS =
    /(asin|acos|atan|atan2|asinh|acosh|atanh|sin|cos|tan|sinh|cosh|tanh|round|floor|ceil|pow|sqrt|log|log2|exp|exp2|fract|clamp|mix|smoothstep|radians|degrees)/g;
    const FLOATS = /[+-]?(?=\d*[.eE])(?=\.?\d)\d*\.?\d*(?:[eE][+-]?\d+)?/g;
    const INTEGERS = /(0[xX][0-9a-fA-F]+[iu]?|[0-9]+[iu]?)/g;

    let result = expression.replace(/\s+/g, '');
    if (result === '') {
    return '';
    }

    // safety check for eval vulnerabilities (https://jsfuck.com/, https://aem1k.com/five/)
    let stripped = result
    .replace(BOOL_FUNCTIONS, '')
    .replace(FLOAT_FUNCTIONS, '')
    .replace(INTEGER_FUNCTIONS, '')
    .replace(FLOATS, '')
    .replace(INTEGERS, '');
    if (!VALID_EXPRESSIONS.test(stripped)) {
    stripped = stripped.replace(/[+\-*/%(,)]+/g, '');
    throw new Error(`Unsupported symbols '${stripped}' in expression ${expression}`);
    }

    // convert to bigint if there are no any floating point numbers or float functions
    let isAbstractInteger = false;
    if (
    (!FLOATS.test(expression) && !FLOAT_FUNCTIONS.test(expression)) ||
    BOOL_FUNCTIONS.test(expression)
    ) {
    result = result.replace(INTEGERS, match => `${parseInt(match)}n`);
    isAbstractInteger = true;
    }

    const mathReplacements = {
    'select(': '((f,t,cond) => cond ? t : f)(',
    'abs(': '((x) => Math.abs(Number(x)))(',
    'min(': '((x,y) => Math.min(Number(x),Number(y)))(',
    'max(': '((x,y) => Math.max(Number(x),Number(y)))(',
    'sign(': '((x) => Math.sign(Number(x)))(',
    'sin(': 'Math.sin(',
    'cos(': 'Math.cos(',
    'tan(': 'Math.tan(',
    'sinh(': 'Math.sinh(',
    'cosh(': 'Math.cosh(',
    'tanh(': 'Math.tanh(',
    'asin(': 'Math.asin(',
    'acos(': 'Math.acos(',
    'atan(': 'Math.atan(',
    'atan2(': 'Math.atan2(',
    'asinh(': 'Math.asinh(',
    'acosh(': 'Math.acosh(',
    'atanh(': 'Math.atanh(',
    'round(': 'Math.round(',
    'floor(': 'Math.floor(',
    'ceil(': 'Math.ceil(',
    'pow(': 'Math.pow(',
    'sqrt(': 'Math.sqrt(',
    'log(': 'Math.log(',
    'log2(': 'Math.log2(',
    'exp(': 'Math.exp(',
    'exp2(': 'Math.pow(2,',
    'fract(': '((x) => x - Math.floor(x))(',
    'clamp(': '((x,min,max) => Math.min(Math.max(x,min),max))(',
    'mix(': '((x,y,a) => x*(1-a) + y*a)(',
    'smoothstep(':
    '((e0,e1,x) => { let t = Math.min(Math.max((x-e0)/(e1-e0),0),1); return t*t*(3-2*t); })(',
    'step(': '((edge,x) => x < edge ? 0 : 1)(',
    'radians(': '((x) => x * Math.PI / 180)(',
    'degrees(': '((x) => x * 180 / Math.PI)('
    };

    result = result.replaceAll('^', '**');
    result = result.replace(/\b\w+\(/g, match => mathReplacements[match] || '');

    try {
    result = Function(`'use strict'; return (${result}).toString();`)();
    return !isAbstractInteger && /^[-+]?\d+$/.test(result) ? `${result}.0` : result;
    } catch (error) {
    throw new Error(`Invalid eval '${expression}' -> '${result}' (${error})`);
    }
    }