1.0.2 โ€ข Published 7 months ago

@antv/expr v1.0.2

Weekly downloads
-
License
MIT
Repository
github
Last release
7 months ago

Lightweight JavaScript expression parser and evaluator, safety and high-performance. ๐Ÿš€

gzip size Build Status npm Version npm Download

Used to parse a mathematical expressions to JavaScript function safely. For example, in @antv/g2, we can set the style with an expressions.

{
  // Equivalent to function: `d => d.value > 100 ? 'red' : 'green'`
  fill: "{ d.value > 100 ? 'red' : 'green' }",
}

โœจ Features

  • ๐Ÿ”’ Secure by default - No access to global objects or prototype chain, does not use eval or new Function.
  • ๐Ÿš€ High performance - Supports pre-compilation of expressions for improved performance with repeated evaluations.
  • ๐Ÿ› ๏ธ Extensible - Register custom functions to easily extend functionality.
  • ๐Ÿชฉ Lightweight - Zero dependencies, small footprint, before gzip it was less than 8 Kb.

๐Ÿ“ฅ Installation

npm install @antv/expr
# or
yarn add @antv/expr
# or
pnpm add @antv/expr

๐Ÿ”จ Usage

Synchronous Expression Evaluation

import { evaluate } from '@antv/expr';

// Basic evaluation
const result = evaluate('x + y', { x: 10, y: 20 }); // returns 30

// Using dot notation and array access
const data = {
  values: [1, 2, 3],
  status: 'active'
};

const result = evaluate('data.values[0] + data.values[1]', { data }); // returns 3

Pre-compiling Expressions

import { compile } from '@antv/expr';

// Compile an expression
const evaluator = compile('price * quantity');
const result1 = evaluator({ price: 10, quantity: 5 }); // returns 50
const result2 = evaluator({ price: 20, quantity: 3 }); // returns 60

Registering and Calling Functions

import { register, evaluate } from '@antv/expr';

// Register functions
register('formatCurrency', (amount) => `$${amount.toFixed(2)}`);

// Function call with arguments
const result = evaluate('@max(a, b, c)', { a: 5, b: 9, c: 2 }); // returns 9

// Expression as function arguments
const result = evaluate('@formatCurrency(price * quantity)', { 
  price: 10.5, quantity: 3 
}); // returns '$31.50'

Build-in Functions: abs, ceil, floor, round, sqrt, pow, max, min.

Variable References

// Simple variable reference
const result = evaluate('x', { x: 42 }); // returns 42

// Nested property access with dot notation
const result = evaluate('user.profile.name', { 
  user: { profile: { name: 'John' } } 
}); // returns 'John'

// Array access with bracket notation
const result = evaluate('items[0]', { items: [10, 20, 30] }); // returns 10

// Mixed dot and bracket notation
const result = evaluate('data.items[0].value', { 
  data: { items: [{ value: 42 }] } 
}); // returns 42

Arithmetic Operations

// Basic arithmetic
const result = evaluate('a + b * c', { a: 5, b: 3, c: 2 }); // returns 11

// Using parentheses for grouping
const result = evaluate('(a + b) * c', { a: 5, b: 3, c: 2 }); // returns 16

// Modulo operation
const result = evaluate('a % b', { a: 10, b: 3 }); // returns 1

Comparison and Logical Operations

// Comparison operators
const result = evaluate('age >= 18', { age: 20 }); // returns true

// Logical AND
const result = evaluate('isActive && !isDeleted', { 
  isActive: true, isDeleted: false 
}); // returns true

// Logical OR
const result = evaluate('status === "active" || status === "pending"', { 
  status: 'pending' 
}); // returns true

Conditional (Ternary) Expressions

// Simple ternary expression
const result = evaluate('age >= 18 ? "adult" : "minor"', { 
  age: 20 
}); // returns 'adult'

// Nested ternary expressions
const result = evaluate('score >= 90 ? "A" : score >= 80 ? "B" : "C"', { 
  score: 85 
}); // returns 'B'

Timeout Handling

You can implement timeout handling by wrapping your evaluation in a Promise.race with a timeout:

import { evaluate } from "@antv/expr";

// Create a function that evaluates with a timeout
function evaluateWithTimeout(expr, context, timeoutMs) {
  const evaluationPromise = new Promise((resolve) => {
    resolve(evaluate(expr, context));
  });

  const timeoutPromise = new Promise((_, reject) => {
    setTimeout(
      () => reject(new Error(`Evaluation timed out after ${timeoutMs}ms`)),
      timeoutMs,
    );
  });

  return Promise.race([evaluationPromise, timeoutPromise]);
}

๐Ÿš€Benchmarks

Performance comparison of different evaluation methods: (baseline: new Function)

Expression Typenew Function vs evaluate after compilenew Function vs evaluate without compilenew Function vs expr-eval Parser
Simple Expressions1.59x faster6.36x faster23.94x faster
Medium Expressions2.16x faster9.81x faster37.81x faster
Complex Expressions1.59x faster4.89x faster32.74x faster
gantt
    title Performance Comparison (Baseline: new Function) * 100
    dateFormat  X
    axisFormat %s

    section Simple
    expr evaluate after compile    :done, 0, 159
    expr evaluate without compile  :done, 0, 636
    expr-eval Parser          :done, 0, 2394

    section Medium
    expr evaluate after compile    :done, 0, 216
    expr evaluate without compile  :done, 0, 981
    expr-eval Parser          :done, 0, 3781

    section Complex
    expr evaluate after compile    :done, 0, 159
    expr evaluate without compile  :done, 0, 489
    expr-eval Parser          :done, 0, 3274

๐Ÿ“ฎAPI Reference

evaluate(expression: string, context?: object): any

Synchronously evaluates an expression and returns the result.

  • expression: The expression string to evaluate
  • context: An object containing variables used in the expression (optional)
  • Returns: The result of the expression evaluation

compile(expression: string): (context?: object) => any

Synchronously compiles an expression, returning a function that can be used multiple times.

  • expression: The expression string to compile
  • Returns: A function that accepts a context object and returns the evaluation result

register(name: string, fn: Function): void

Registers a custom function that can be used in expressions.

  • name: Function name (used with @ prefix in expressions)
  • fn: Function implementation

All evaluation errors throw an ExpressionError type exception with detailed error information.

License

MIT

1.0.2

7 months ago

1.0.1

7 months ago

1.0.0

7 months ago