Skip to content

Commit

Permalink
feat: add registerPlugin method
Browse files Browse the repository at this point in the history
Can now call `JseEval.registerPlugin` (same as `JseEval.jsep.plugins.register(...)`). This method will now look at the plugin argument(s) and check for `init()` to call `jsep.plugins.register`, as well as look for `initEval()` to call that method so it can extend JseEval as needed.

Updated ReadMe to add more extension examples

Fixes #52
  • Loading branch information
6utt3rfly committed Mar 13, 2022
1 parent d87fd03 commit 623f677
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 13 deletions.
64 changes: 58 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# jse-eval

[![Latest NPM release](https://img.shields.io/npm/v/jse-eval.svg)](https://www.npmjs.com/package/jse-eval)
[![Minzipped size](https://badgen.net/bundlephobia/minzip/jse-eval)](https://bundlephobia.com/result?p=jse-eval)
[![License](https://img.shields.io/badge/license-MIT-007ec6.svg)](https://github.com/donmccurdy/jse-eval/blob/master/LICENSE)
[![CI](https://github.com/6utt3rfly/jse-eval/workflows/CI/badge.svg?branch=main&event=push)](https://github.com/6utt3rfly/jse-eval/actions?query=workflow%3ACI)
<!-- [![Minzipped size](https://badgen.net/bundlephobia/minzip/jse-eval)](https://bundlephobia.com/result?p=jse-eval) -->

## Credits
Heavily based on [expression-eval](https://github.com/donmccurdy/expression-eval) and [jsep](https://github.com/EricSmekens/jsep),
Expand Down Expand Up @@ -48,8 +48,10 @@ Import:
```js
// ES6
import { parse, evaluate, compile, jsep } from 'jse-eval';

// CommonJS
const { parse, evaluate, compile, jsep } = require('jse-eval');

// UMD / standalone script
const { parse, evaluate, compile, jsep } = window.expressionEval;
```
Expand Down Expand Up @@ -105,10 +107,20 @@ const fn = compileAsync('foo.bar + 10');
fn({foo: {bar: 'baz'}}); // 'baz10'
```

### One-Line Parse + Evaluation
```javascript
import { evalExpr } from 'jse-eval';
evalExpr('foo.bar + 10', {foo: {bar: 'baz'}}); // baz10

// alternatively:
import { evalExprAsync } from 'jse-eval';
evalExprAsync('foo.bar + 10', {foo: {bar: 'baz'}}); // baz10
```

### JSEP Plugins
```javascript
const { jsep } = require('jse-eval');
jsep.plugins.register(
import { registerPlugin } from 'jse-eval';
registerPlugin(
require('@jsep-plugin/arrow'),
require('@jsep-plugin/assignment'),
require('@jsep-plugin/async-await'),
Expand All @@ -119,6 +131,14 @@ jsep.plugins.register(
require('@jsep-plugin/template'),
require('@jsep-plugin/ternary')
);

// or alternatively:
const { jsep } = require('jse-eval');
jsep.plugins.register(
require('@jsep-plugin/arrow'),
require('@jsep-plugin/assignment'),
...
);
```

## Extending evaluation
Expand All @@ -135,21 +155,53 @@ or `this.evalSyncAsync()` to help.
throwing an error for an unknown node type. If any other behavior is desired, this can be overridden
by providing a new `default` evaluator.

Extensions may also be added as plugins using the `registerPlugin(myPlugin1, myPlugin2...)` method.
The plugins are extensions of the JSEP format. If the `init` method is defined in the plugin,
then the plugin will be added to JSEP, and/or if the `initEval` method is defined in the plugin,
then the `initEval` method will be called with the JseEval class as both `this` and as an argument
so the plugin code may extend as necessary.

### Example Extensions:
```javascript
import * as expr from 'jse-eval';

expr.addBinaryOp('**', 11, true, (a, b) => a ** b);
console.log(expr.evalExpr('2 ** 3 ** 2')); // 512

expr.addBinaryOp('^', 11, (a, b) => Math.pow(a, b)); // Replace XOR with Exponent
console.log(expr.evalExpr('3^2')); // 9

expr.addEvaluator('TestNodeType', function(node) {
return node.test + this.context.string
});
console.log(expr.eval({ type: 'TestNodeType', test: 'testing ' }, { string: 'jse-eval' })); // 'testing jse-eval'

const myPlugin = {
name: 'Exponentiation',
init(jsep) {
jsep.addBinaryOp('**', 11, true);
},
initEval(JseEval) {
JseEval.binops['**'] = (a, b) => a ** b;
},
};
expr.registerPlugin(myPlugin);
console.log(expr.evalExpr('2 ** 3 ** 2')); // 512
```

### Node Types Supported:
This project will try to stay current with all JSEP's node types::
- `ArrayExpression`
- `LogicalExpression`/`BinaryExpression`
- `CallExpression`
- `ConditionalExpression`
- `Compound` *
- `Compound` *Compound support will evaluate each expression and return the result of the final one*
- `Identifier`
- `Literal`
- `MemberExpression`
- `ThisExpression`
- `UnaryExpression`

**Compound support will evaluate each expression and return the result of the final one

As well as the optional plugin node types:
- `ArrowFunctionExpression`
- `AssignmentExpression`/`UpdateExpression`
Expand Down
36 changes: 29 additions & 7 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ type AnyExpression = jsep.ArrayExpression
| TemplateElement
;

type JseEvalPlugin = Partial<jsep.IPlugin> & {
initEval?: (this: typeof ExpressionEval, jseEval: typeof ExpressionEval) => void;
}

export default class ExpressionEval {
static jsep = jsep;
static parse = jsep;
Expand Down Expand Up @@ -178,28 +182,46 @@ export default class ExpressionEval {
ExpressionEval.evaluators[nodeType] = evaluator;
}

static registerPlugin(...plugins: Array<JseEvalPlugin>) {
plugins.forEach((p) => {
if (p.init) {
ExpressionEval.parse.plugins.register(p as jsep.IPlugin);
}
if (p.initEval) {
p.initEval.call(ExpressionEval, ExpressionEval);
}
});
}

// main evaluator method
static eval(ast: jsep.Expression, context: Context): unknown {
static eval(ast: jsep.Expression, context?: Context): unknown {
return (new ExpressionEval(context)).eval(ast);
}
static async evalAsync(ast: jsep.Expression, context: Context): Promise<unknown> {
static async evalAsync(ast: jsep.Expression, context?: Context): Promise<unknown> {
return (new ExpressionEval(context, true)).eval(ast);
}

// compile an expression and return an evaluator
static compile(expression: string): (context: Context) => unknown {
static compile(expression: string): (context?: Context) => unknown {
return ExpressionEval.eval.bind(null, ExpressionEval.jsep(expression));
}
static compileAsync(expression: string): (context: Context) => Promise<unknown> {
static compileAsync(expression: string): (context?: Context) => Promise<unknown> {
return ExpressionEval.evalAsync.bind(null, ExpressionEval.jsep(expression));
}

// compile and evaluate
static evalExpr(expression: string, context?: Context): unknown {
return ExpressionEval.compile(expression)(context);
}
static evalExprAsync(expression: string, context?: Context): unknown {
return ExpressionEval.compileAsync(expression)(context);
}


protected context: Context;
protected isAsync: boolean;
protected context?: Context;
protected isAsync?: boolean;

constructor(context: Context, isAsync?: boolean) {
constructor(context?: Context, isAsync?: boolean) {
this.context = context;
this.isAsync = isAsync;
}
Expand Down

0 comments on commit 623f677

Please sign in to comment.