diff --git a/packages/expression/README.md b/packages/expression/README.md new file mode 100644 index 00000000..febbf6ff --- /dev/null +++ b/packages/expression/README.md @@ -0,0 +1,16 @@ +[![Build Status][build-image]][build-url] +[![NPM version][npm-image]][npm-url] + + +# Gabliam amqp + +Gabliam plugin for add amqp. + +# License + + MIT + +[build-image]: https://img.shields.io/travis/gabliam/gabliam/master.svg?style=flat-square +[build-url]: https://travis-ci.org/gabliam/gabliam +[npm-image]: https://img.shields.io/npm/v/@gabliam/amqp.svg?style=flat-square +[npm-url]: https://github.com/gabliam/amqp diff --git a/packages/expression/package.json b/packages/expression/package.json new file mode 100644 index 00000000..e6c5038a --- /dev/null +++ b/packages/expression/package.json @@ -0,0 +1,42 @@ +{ + "name": "@gabliam/expression", + "version": "6.1.0", + "description": "expression parser", + "main": "lib/index.js", + "typings": "lib/index.d.ts", + "module": "lib/index.js", + "scripts": { + "build": "rimraf lib && tsc -p tsconfig.build.json" + }, + "files": [ + "lib", + "src" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/gabliam/gabliam.git" + }, + "keywords": [ + "amqp", + "gabliam" + ], + "author": "David Touzet ", + "license": "MIT", + "bugs": { + "url": "https://github.com/gabliam/gabliam/issues" + }, + "homepage": "https://github.com/gabliam/gabliam/tree/master/packages/amqp#readme", + "peerDependencies": { + "@gabliam/core": "^6.1.0" + }, + "devDependencies": { + "@types/escodegen": "^0.0.6", + "@types/esprima": "^4.0.1", + "@types/node": "^8.0.14" + }, + "dependencies": { + "escodegen": "^1.9.0", + "esprima": "^4.0.0", + "tslib": "^1.8.0" + } +} diff --git a/packages/expression/src/expression-parser.ts b/packages/expression/src/expression-parser.ts new file mode 100644 index 00000000..28de5a5f --- /dev/null +++ b/packages/expression/src/expression-parser.ts @@ -0,0 +1,14 @@ +import * as esprima from 'esprima'; +import { ExpressionStatement } from 'estree'; +import { Expression } from './expression'; + +export class ExpressionParser { + constructor(private context: object = {}) { + } + + parseExpression(input: string) { + const program = esprima.parseScript(input); + const ast = (program.body[0]).expression; + return new Expression(ast, this.context); + } +} diff --git a/packages/expression/src/expression.ts b/packages/expression/src/expression.ts new file mode 100644 index 00000000..7fcba0f5 --- /dev/null +++ b/packages/expression/src/expression.ts @@ -0,0 +1,17 @@ +import { Expression as AstExpression } from 'estree'; +import { Parser } from './parser'; + +export class Expression { + private parser: Parser; + + constructor(ast: AstExpression, private context: object = {}) { + this.parser = new Parser(ast); + } + + getValue(vars: object = {}): T | undefined | null { + return this.parser.parse({ + ...this.context, + ...vars + }); + } +} diff --git a/packages/expression/src/parser.ts b/packages/expression/src/parser.ts new file mode 100644 index 00000000..300f7e45 --- /dev/null +++ b/packages/expression/src/parser.ts @@ -0,0 +1,378 @@ +// tslint:disable:no-bitwise +import { generate } from 'escodegen'; +import { + Expression, + UnaryExpression, + ArrayExpression, + SpreadElement, + ObjectExpression, + Pattern, + BinaryExpression, + LogicalExpression, + Identifier, + ThisExpression, + CallExpression, + Super, + MemberExpression, + ConditionalExpression, + ExpressionStatement, + ReturnStatement, + FunctionExpression, + TemplateLiteral, + TaggedTemplateExpression, + TemplateElement, + Statement +} from 'estree'; +import { Symbol } from 'bson'; + +const FAIL = new Symbol('FAIL'); + +export class Parser { + constructor(private ast: Expression) {} + + parse(vars: object = {}): T | undefined | null { + const result = this.walk(this.ast, vars); + return result === FAIL ? undefined : result; + } + + private parseUnary(node: UnaryExpression, vars: object) { + const val = this.walk(node.argument, vars); + switch (node.operator) { + case '+': + return +val; + case '-': + return -val; + case '~': + return ~val; + case '!': + return !val; + case 'typeof': + return typeof val; + case 'void': + case 'delete': + return undefined; + default: + return FAIL; + } + } + + private parseArray(node: ArrayExpression, vars: object) { + const xs: any[] = []; + for (const nodeElement of node.elements) { + const x = this.walk(nodeElement, vars); + if (x === FAIL) { + return FAIL; + } + xs.push(x); + } + return xs; + } + + private parseObject(node: ObjectExpression, vars: object) { + const obj = {}; + for (let i = 0; i < node.properties.length; i++) { + const prop = node.properties[i]; + const value: any = + prop.value === null ? prop.value : this.walk(prop.value, vars); + if (value === FAIL) { + return FAIL; + } + const key: any = prop.key === null ? prop.key : this.walk(prop.key, vars); + if (key === FAIL) { + return FAIL; + } + (obj)[key] = value; + } + return obj; + } + + private parseLeftRight( + node: BinaryExpression | LogicalExpression, + vars: object + ) { + const l = this.walk(node.left, vars); + if (l === FAIL) { + return [FAIL]; + } + const r = this.walk(node.right, vars); + if (r === FAIL) { + return [FAIL]; + } + return [l, r]; + } + + private parseBinary(node: BinaryExpression, vars: object) { + const [l, r] = this.parseLeftRight(node, vars); + if (l === FAIL) { + return FAIL; + } + switch (node.operator) { + case '==': + // tslint:disable-next-line:triple-equals + return l == r; + + case '!=': + // tslint:disable-next-line:triple-equals + return l != r; + + case '===': + return l === r; + + case '!==': + return l !== r; + + case '<': + return l < r; + + case '<=': + return l <= r; + + case '>': + return l > r; + + case '>=': + return l >= r; + + case '<<': + return l << r; + + case '>>': + return l >> r; + + case '>>>': + return l >>> r; + + case '+': + return l + r; + + case '-': + return l - r; + + case '*': + return l * r; + + case '/': + return l / r; + + case '%': + return l % r; + + case '**': + return l ** r; + + case '|': + return l | r; + + case '^': + return l ^ r; + + case '&': + return l & r; + + case 'in': + return l in r; + + case 'instanceof': + return l instanceof r; + } + } + + private parseLogical(node: LogicalExpression, vars: object) { + const [l, r] = this.parseLeftRight(node, vars); + if (l === FAIL) { + return FAIL; + } + switch (node.operator) { + case '||': + return l || r; + case '&&': + return l && r; + } + } + + private parseIdentifier(node: Identifier, vars: object) { + if ({}.hasOwnProperty.call(vars, node.name)) { + return (vars)[node.name]; + } + return undefined; + } + + private parseThis(node: ThisExpression, vars: object) { + if ({}.hasOwnProperty.call(vars, 'this')) { + return (vars)['this']; + } + return FAIL; + } + + private parseCall(node: CallExpression, vars: object) { + const callee = this.walk(node.callee, vars); + if (callee === FAIL) { + return FAIL; + } + if (typeof callee !== 'function') { + return FAIL; + } + + let ctx = (node.callee).object + ? this.walk((node.callee).object, vars) + : FAIL; + if (ctx === FAIL) { + ctx = null; + } + + const args = []; + for (const arg of node.arguments) { + const x = this.walk(arg, vars); + if (x === FAIL) { + return FAIL; + } + args.push(x); + } + return callee.apply(ctx, args); + } + + private parseMember(node: MemberExpression, vars: object) { + const obj = this.walk(node.object, vars); + if (obj === undefined) { + return undefined; + } + + // do not allow access to methods on Function + if (obj === FAIL || typeof obj === 'function') { + return FAIL; + } + if (node.property.type === 'Identifier') { + return obj[node.property.name]; + } + const prop = this.walk(node.property, vars); + if (prop === FAIL) { + return FAIL; + } + return obj[prop]; + } + + private parseConditional(node: ConditionalExpression, vars: object) { + const val = this.walk(node.test, vars); + if (val === FAIL) { + return FAIL; + } + return val + ? this.walk(node.consequent, vars) + : this.walk(node.alternate, vars); + } + + private parseStatement(node: ExpressionStatement, vars: object) { + const val = this.walk(node.expression, vars); + if (val === FAIL) { + return FAIL; + } + return val; + } + + private parseReturnStatement(node: ReturnStatement, vars: object) { + return this.walk(node.argument, vars); + } + + private parseFunction(node: FunctionExpression, vars: object) { + const bodies = node.body.body; + + const newVars = { + ...vars + }; + + node.params.forEach(key => { + if (key.type === 'Identifier') { + (newVars)[key.name] = null; + } + }); + + for (const i in bodies) { + if (this.walk(bodies[i], newVars) === FAIL) { + return FAIL; + } + } + + const keys = Object.keys(vars); + const vals = keys.map(key => { + return (vars)[key]; + }); + return Function(keys.join(', '), 'return ' + generate(node)).apply( + null, + vals + ); + } + + private parseTemplateLiteral(node: TemplateLiteral, vars: object) { + let str = ''; + let i: number; + for (i = 0; i < node.expressions.length; i++) { + str += this.walk(node.quasis[i], vars); + str += this.walk(node.expressions[i], vars); + } + str += this.walk(node.quasis[i], vars); + return str; + } + + private parseTaggedTemplate(node: TaggedTemplateExpression, vars: object) { + const tag = this.walk(node.tag, vars); + const quasi = node.quasi; + const strings = quasi.quasis.map(q => this.walk(q, vars)); + const values = quasi.expressions.map(e => this.walk(e, vars)); + return tag.apply(null, [strings].concat(values)); + } + + private walk( + node: + | Expression + | SpreadElement + | Pattern + | Super + | TemplateElement + | Statement + | null + | undefined, + vars: object + ): any { + if (!node) { + return FAIL; + } + switch (node.type) { + case 'Literal': + return node.value; + case 'UnaryExpression': + return this.parseUnary(node, vars); + case 'ArrayExpression': + return this.parseArray(node, vars); + case 'ObjectExpression': + return this.parseObject(node, vars); + case 'BinaryExpression': + return this.parseBinary(node, vars); + case 'LogicalExpression': + return this.parseLogical(node, vars); + case 'Identifier': + return this.parseIdentifier(node, vars); + case 'ThisExpression': + return this.parseThis(node, vars); + case 'CallExpression': + return this.parseCall(node, vars); + case 'MemberExpression': + return this.parseMember(node, vars); + case 'ConditionalExpression': + return this.parseConditional(node, vars); + case 'ExpressionStatement': + return this.parseStatement(node, vars); + case 'ReturnStatement': + return this.parseReturnStatement(node, vars); + case 'FunctionExpression': + return this.parseFunction(node, vars); + case 'TemplateLiteral': + return this.parseTemplateLiteral(node, vars); + case 'TaggedTemplateExpression': + return this.parseTaggedTemplate(node, vars); + case 'TemplateElement': + return node.value.cooked; + default: + return FAIL; + } + } +} diff --git a/packages/expression/src/test.ts b/packages/expression/src/test.ts new file mode 100644 index 00000000..6b379e2f --- /dev/null +++ b/packages/expression/src/test.ts @@ -0,0 +1,18 @@ +import { ExpressionParser } from './expression-parser'; + + +const parser = new ExpressionParser({ + hello: (name: string) => 'hello ' + name +}); +const expression = parser.parseExpression('hello($user.name)'); +console.log(expression.getValue({ + '$user': { + name: 'Darth Vader' + }})); + + console.log(expression.getValue({ + '$user': { + name: 'Luke' + }})); + + console.log(expression.getValue()); diff --git a/packages/expression/tsconfig.build.json b/packages/expression/tsconfig.build.json new file mode 100644 index 00000000..ad4d6298 --- /dev/null +++ b/packages/expression/tsconfig.build.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { + "sourceMap": false, + "outDir": "lib" + }, + "include":[ + "src/**/*.ts", + "custom-typings.d.ts" + ] +} diff --git a/yarn.lock b/yarn.lock index b732e4c2..94eaa3d6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -48,6 +48,20 @@ version "0.0.30" resolved "https://registry.yarnpkg.com/@types/debug/-/debug-0.0.30.tgz#dc1e40f7af3b9c815013a7860e6252f6352a84df" +"@types/escodegen@^0.0.6": + version "0.0.6" + resolved "https://registry.yarnpkg.com/@types/escodegen/-/escodegen-0.0.6.tgz#5230a9ce796e042cda6f086dbf19f22ea330659c" + +"@types/esprima@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@types/esprima/-/esprima-4.0.1.tgz#40f6fb3a3a50ebcaa4dfca17f41e7f10e687df03" + dependencies: + "@types/estree" "*" + +"@types/estree@*": + version "0.0.38" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.38.tgz#c1be40aa933723c608820a99a373a16d215a1ca2" + "@types/express-serve-static-core@*": version "4.0.56" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.0.56.tgz#4ed556dcff9012cce6b016e214fdc5ef6e99db7d" @@ -1862,7 +1876,7 @@ escodegen@1.8.x: optionalDependencies: source-map "~0.2.0" -escodegen@1.x.x, escodegen@^1.6.1: +escodegen@1.x.x, escodegen@^1.6.1, escodegen@^1.9.0: version "1.9.0" resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.9.0.tgz#9811a2f265dc1cd3894420ee3717064b632b8852" dependencies: