@ibyar/expressions
Version:
Aurora expression, an template expression and evaluation, An 100% spec compliant ES2022 JavaScript toolchain,
327 lines (253 loc) • 9.01 kB
Markdown
# Ibyar Expression
[![NPM Version][npm-image]][npm-url]
[![NPM Downloads][downloads-image]][downloads-url]
[![LICENSE][license-img]][license-url]
[![lerna][lerna-img]][lerna-url]
![GitHub contributors][contributors]
[npm-image]: https://img.shields.io/npm/v/@ibyar/expressions.svg?logo=npm&logoColor=fff&label=NPM+package&color=limegreen
[npm-url]: https://npmjs.org/package/@ibyar/expressions
[downloads-image]: https://img.shields.io/npm/dt/@ibyar/expressions
[downloads-url]: https://npmjs.org/package/@ibyar/expressions
[license-img]: https://img.shields.io/github/license/ibyar/aurora
[license-url]: https://github.com/ibyar/aurora/blob/master/LICENSE
[lerna-img]: https://img.shields.io/badge/maintained%20with-lerna-cc00ff.svg
[lerna-url]: https://lerna.js.org/
[contributors]: https://img.shields.io/github/contributors/ibyar/aurora
Your Mirror Dimension in the javascript world.
Ibyar expression, is a JavaScript expression parsing and evaluation. and template expression evaluation, with stack and scope.
- this package has no dependance, can work on both (Node.js) and any Web Browsers, that support modules and class syntax
- An 100% spec compliant ES2022 JavaScript toolchain
## `Install`
``` bash
npm i --save @ibyar/expressions
```
``` bash
yarn add @ibyar/expressions
```
## Try the parser
- Ibyar Expression parser: https://ibyar.github.io/astexplorer/
## Example
```ts
import { JavaScriptParser, Scope, Stack } from '@ibyar/expressions';
const source = `
let { x, y } = {x: 5, y: 55};
const z = x / y;
console.log({x, y, z});
const alex = { firstName: 'alex', lastName: 'jon', age: 30 };
alex.fullName = function(){ return this.firstName + ' ' + this.lastName;};
console.log(alex.fullName());
console.log(alex.fullName.toString());
setTimeout(() => console.log('setTimeout', alex), 500);
const sara = { firstName: 'sara', lastName: 'jon', age: 28, [Symbol.toStringTag]: 'SARA'};
console.log(sara['age']);
console.log('toStringTag', Object.prototype.toString.call(sara));
console.log('compare', sara.age <=> alex.age);
console.log('older', sara.age >? alex.age);
console.log('younger', sara.age alex.age);
console.log('typeof', typeof alex);
console.log('typeof', typeof alex.age);
console.log('regex 1', /regex/g.test('reg'));
console.log('regex 2', /regex/g.test('regex'));
`
+
'let stringLiteralExample = `${alex.firstName} and ${sara.firstName} are friends`; console.log({stringLiteralExample});'
+
`
function latex(str) {
return { "cooked": str[0], "raw": str.raw[0] }
}
`
+
'let taggedStringLiteral = latex`Hi\n${2+3}!`; console.log({taggedStringLiteral});'
;
const pipeSource = `
const add = (arg1, arg2) => arg1 + arg2;
a
|> function(s) {return s;}
|> (c => b = c + 1)
|> add(?, 2)
|> add(3, ?)
|> console.log:4:5;`;
const ast = JavaScriptParser.parse(source + pipeSource);
const esTree = ast.toJSON();
const esTreeString = JSON.stringify(ast, void 0, 2);
console.log({ esTree, esTreeString });
const context = {
Promise,
setTimeout,
console,
Symbol,
Object
};
const stack = Stack.for(context);
const globalScope = Scope.functionScopeFor({
a: 7,
b: undefined
});
stack.pushScope(globalScope);
ast.get(stack);
console.log(
'from global context values: x: %s, y: %s',
globalScope.getContext().x,
globalScope.getContext().y
);
console.log(
globalScope.getContext()
);
```
## follow ast of [ESTree](https://github.com/estree/estree/)
### Feature
- [V8 JavaScript engine](https://github.com/v8/v8) to parse js source code.
- for now this parser does not provide any info about `SourceLocation`
- class expression
- Import/Export module
- Custom Factory Builder to convert the code to ESTree directly or to executable expression node
## Operators
| Operator type | Individual operators |
| ---------------------- | ------------------------------------------------------- |
| member | `. []` |
| call / create instance | `() new` |
| negation/increment | `! ~ - + ++ -- typeof void delete` |
| multiply/divide | `* / % ** %%` |
| addition/subtraction | `+ -` |
| bitwise shift | `<< >> >>>` |
| relational | `< <= > >= >? <? <=> in instanceof` |
| equality | `== != === !==` |
| bitwise-and | `&` |
| bitwise-xor | `^` |
| bitwise-or | `\|` |
| logical-and | `&&` |
| logical-or | `\|\|` |
| conditional | `?:` |
| pipeline | `\|>` |
| assignment | `= += -= *= **= /= %= <<= >>= >>>= &= ^= \|= &&= \|\|= ??= >?= <?= %%=` |
| comma | `,` |
non-ecma operator are `%% >? <? <=> >?= <?= %%=`
## Pipeline Operator |> Support
- support angular-like syntax and partial operator for a call syntax:
```js
x |> methodName1:arg2:arg3
|> methodName2(arg1, ?, ...arg3);
function add(x, y, z) { return x + y + z };
const a = 88;
const b = 99;
const c = 11;
const z = a |> add:b:c; // === add(a, b, c)
```
```js
function map(num){
return { number: num };
}
function plus(num1){
return function(num2){
return num1 + num2;
};
}
function add(x, y) { return x + y };
const a = 88, b = 11;
b |> function(x) { console.log(x); return x; }
|> x => { console.log(x); return x; }
|> (x => x + 10)
|> plus(a)
|> add(99, ?)
|> add : 88 : ?
|> add : ? : 77
|> map
```
### How to use stack and scope:
1- create stack:
```ts
import { Scope, Stack } from '@ibyar/expressions';
const stack1 = new Stack();
// OR
const scope1 = new Scope<{x: number, y: number}>( {x: 8, y: 6});
const scope2 = Scope.for({z: 9});
const stack2 = new Stack([scope1, scope2]);
```
2- any of the previous stacks, does not provide and public api like `Math`, `String`, ....
you need to provide these values to the stack, the stack work as a sandbox, isolated from the js-VM.
```ts
import { ReadOnlyScope } from '@ibyar/expressions';
stack1.pushScope(ReadOnlyScope.for({
Math: {
E: Math.E,
LN10: Math.LN10,
LN2: Math.LN2,
LOG10E: Math.LOG10E,
LOG2E: Math.LOG2E,
PI: Math.PI,
SQRT1_2: Math.SQRT1_2,
SQRT2: Math.SQRT2,
abs: Math.abs,
acos: Math.acos,
asin: Math.asin,
atan: Math.atan,
atan2: Math.atan2,
ceil: Math.ceil,
....
},
// or
Math,
// another object
Array: {
// in js a statement like `let x = new Array()` will throw exception.
isArray: Array.isArray,
},
Array, // `let x = new Array()` no Error.
parseFloat,
parseInt,
String,
console,
}));
```
so now you can write js code the use these as identifier
3- the stack is ready to use with expression
4- try to parse an expression like `let x = 'data'; console.log(x);`.
```ts
import { JavaScriptParser } from '@ibyar/expressions';
const ast = JavaScriptParser.parse(`let x = 'data'; console.log(x);`);
// use get method to execute an expression and return a value if available.
ast.get(stack1);
// it should print 'data' on the console
```
5- you can visit the ast
```ts
import { expressionVisitor, VariableDeclaration } from '@ibyar/expressions';
expressionVisitor.visit(ast, (expression, type, control) => {
if (type === 'VariableDeclaration') {
const kind = (expression as VariableDeclaration).getKind();
....
}
});
```
### How to use Reactive Scope:
reactive scope is used to
```ts
import { JavaScriptParser, ReactiveScope, Stack } from '@ibyar/expressions';
// parse JS code
const ast = JavaScriptParser.parse(`
const x = 'Hello';
const y = "World";
let z = x + ' ' + y + '!';
z = 123 + 456;
z = (x + ' ' + y).toUpperCase();
z = 'Reactive Scope is Awesome!!';
`);
// create reactive scope, can listen to changes of variables
const reactiveScope = ReactiveScope.blockScope();
// listen for changes of 'z' variable
const zSubscription = reactiveScope.subscribe('z', (nz, oz) => {
console.log('new value of Z = ' + nz, 'old value of Z = ' + oz);
});
// listen for changes of 'z' const
const xSubscription = reactiveScope.subscribe('x', (nx, ox) => {
console.log('new value of X = ' + nx, 'old value of X = ' + ox);
});
// create stack
const stack = new Stack(reactiveScope);
// execute js code
ast.get(stack);
// unsubscribe from all created ScopeSubscription
zSubscription.unsubscribe();
xSubscription.unsubscribe();
```