math-codegen
Advanced tools
Comparing version 0.2.5 to 0.3.0
@@ -7,3 +7,3 @@ /* | ||
*/ | ||
'use strict'; | ||
module.exports = require('./lib/CodeGenerator'); | ||
'use strict' | ||
module.exports = require('./lib/CodeGenerator') |
@@ -1,69 +0,87 @@ | ||
/** | ||
* Created by mauricio on 5/12/15. | ||
*/ | ||
'use strict'; | ||
'use strict' | ||
var esprima = require('esprima'); | ||
var Interpreter = require('./Interpreter'); | ||
var extend = require('extend'); | ||
var Parser = require('mr-parser').Parser | ||
var Interpreter = require('./Interpreter') | ||
var extend = require('extend') | ||
function CodeGenerator(options, defs) { | ||
this.statements = []; | ||
this.defs = defs || {}; | ||
this.interpreter = new Interpreter(this, options); | ||
function CodeGenerator (options, defs) { | ||
this.statements = [] | ||
this.defs = defs || {} | ||
this.interpreter = new Interpreter(this, options) | ||
} | ||
CodeGenerator.prototype.setDefs = function (defs) { | ||
this.defs = extend(this.defs, defs); | ||
return this; | ||
}; | ||
this.defs = extend(this.defs, defs) | ||
return this | ||
} | ||
CodeGenerator.prototype.compile = function (namespace) { | ||
if (!namespace) { | ||
throw Error('namespace required'); | ||
if (!namespace || typeof namespace !== 'object') { | ||
throw TypeError('namespace must be an object') | ||
} | ||
if (typeof namespace.factory !== 'function') { | ||
throw TypeError('namespace.factory must be a function') | ||
} | ||
// default process scope hook | ||
this.defs.ns = namespace; | ||
this.defs.$$processScope = this.defs.$$processScope || function () {}; | ||
// definitions available in the function | ||
// each property under this.defs is mapped to local variables | ||
// e.g | ||
// | ||
// function (defs) { | ||
// var ns = defs['ns'] | ||
// // code generated for the expression | ||
// } | ||
this.defs.ns = namespace | ||
this.defs.$$mathCodegen = { | ||
throwUndefined: function (symbol) { | ||
throw SyntaxError('symbol "' + symbol + '" is undefined') | ||
}, | ||
functionProxy: function (fn, name) { | ||
if (typeof fn !== 'function') { | ||
throw SyntaxError('symbol "' + name + '" must be a function') | ||
} | ||
return fn | ||
} | ||
} | ||
this.defs.$$processScope = this.defs.$$processScope || function () {} | ||
var defsCode = Object.keys(this.defs).map(function (name) { | ||
return 'var ' + name + ' = defs["' + name + '"];'; | ||
}); | ||
return 'var ' + name + ' = defs["' + name + '"]' | ||
}) | ||
// statement join | ||
if (!this.statements.length) { | ||
throw Error('there are no statements saved in this generator'); | ||
throw Error('there are no statements saved in this generator') | ||
} | ||
// last statement is always a return statement | ||
this.statements[this.statements.length - 1] = 'return ' + this.statements[this.statements.length - 1]; | ||
this.statements[this.statements.length - 1] = 'return ' + this.statements[this.statements.length - 1] | ||
var code = this.statements.join(';'); | ||
var factoryCode = | ||
defsCode.join(' ') + | ||
'return {' + | ||
' eval: function (scope) {' + | ||
' scope = scope || {};' + | ||
' $$processScope(scope);' + | ||
' ' + code + | ||
' },' + | ||
' code: \'' + code + '\'' + | ||
'};'; | ||
var code = this.statements.join(';') | ||
var factoryCode = defsCode.join('\n') + '\n' + [ | ||
'return {', | ||
' eval: function (scope) {', | ||
' scope = scope || {}', | ||
' $$processScope(scope)', | ||
' ' + code, | ||
' },', | ||
" code: '" + code + "'", | ||
'}' | ||
].join('\n') | ||
/* eslint-disable */ | ||
var factory = new Function('defs', factoryCode); | ||
var factory = new Function('defs', factoryCode) | ||
return factory(this.defs) | ||
/* eslint-enable */ | ||
return factory(this.defs); | ||
}; | ||
} | ||
CodeGenerator.prototype.parse = function (code) { | ||
var self = this; | ||
var program = esprima.parse(code); | ||
this.statements = program.body.map(function (statement) { | ||
return self.interpreter.next(statement); | ||
}); | ||
return this; | ||
}; | ||
var self = this | ||
var program = new Parser().parse(code) | ||
this.statements = program.blocks.map(function (statement) { | ||
return self.interpreter.next(statement) | ||
}) | ||
return this | ||
} | ||
module.exports = CodeGenerator; | ||
module.exports = CodeGenerator |
@@ -1,20 +0,17 @@ | ||
'use strict'; | ||
var extend = require('extend'); | ||
'use strict' | ||
var extend = require('extend') | ||
var types = { | ||
// node | ||
ArrayExpression: require('./node/ArrayExpression'), | ||
AssignmentExpression: require('./node/AssignmentExpression'), | ||
BinaryExpression: require('./node/BinaryExpression'), | ||
CallExpression: require('./node/CallExpression'), | ||
ConditionalExpression: require('./node/ConditionalExpression'), | ||
ExpressionStatement: require('./node/ExpressionStatement'), | ||
UnaryExpression: require('./node/UnaryExpression'), | ||
// misc | ||
Identifier: require('./misc/Identifier'), | ||
Literal: require('./misc/Literal') | ||
}; | ||
ArrayNode: require('./node/ArrayNode'), | ||
AssignmentNode: require('./node/AssignmentNode'), | ||
ConditionalNode: require('./node/ConditionalNode'), | ||
ConstantNode: require('./node/ConstantNode'), | ||
FunctionNode: require('./node/FunctionNode'), | ||
OperatorNode: require('./node/OperatorNode'), | ||
SymbolNode: require('./node/SymbolNode'), | ||
UnaryNode: require('./node/UnaryNode') | ||
} | ||
var Interpreter = function (owner, options) { | ||
this.owner = owner; | ||
this.owner = owner | ||
this.options = extend({ | ||
@@ -25,26 +22,26 @@ factory: 'ns.factory', | ||
rawCallExpressionElements: false | ||
}, options); | ||
}; | ||
}, options) | ||
} | ||
extend(Interpreter.prototype, types); | ||
extend(Interpreter.prototype, types) | ||
// run is the main method which decides which expression to call | ||
// main method which decides which expression to call | ||
Interpreter.prototype.next = function (node) { | ||
if (!(node.type in this)) { | ||
throw new TypeError('the node type ' + node.type + ' is not implemented'); | ||
throw new TypeError('the node type ' + node.type + ' is not implemented') | ||
} | ||
return this[node.type](node); | ||
}; | ||
return this[node.type](node) | ||
} | ||
Interpreter.prototype.rawify = function (test, fn) { | ||
var oldRaw = this.options.raw; | ||
var oldRaw = this.options.raw | ||
if (test) { | ||
this.options.raw = true; | ||
this.options.raw = true | ||
} | ||
fn(); | ||
fn() | ||
if (test) { | ||
this.options.raw = oldRaw; | ||
this.options.raw = oldRaw | ||
} | ||
}; | ||
} | ||
module.exports = Interpreter; | ||
module.exports = Interpreter |
{ | ||
"name": "math-codegen", | ||
"version": "0.2.5", | ||
"version": "0.3.0", | ||
"description": "Generates code from mathematical expressions", | ||
@@ -27,4 +27,4 @@ "bugs": "https://github.com/maurizzzio/math-codegen/issues", | ||
"dependencies": { | ||
"esprima": "^2.2.0", | ||
"extend": "^3.0.0" | ||
"extend": "^3.0.0", | ||
"mr-parser": "^0.1.0" | ||
}, | ||
@@ -34,16 +34,16 @@ "devDependencies": { | ||
"doctoc": "^0.14.2", | ||
"eslint": "^0.21.0", | ||
"istanbul": "^0.3.8", | ||
"mocha": "^2.2.1", | ||
"mocha-lcov-reporter": "^0.0.2", | ||
"nodemon": "^1.3.7" | ||
"nodemon": "^1.3.7", | ||
"standard": "^4.5.4" | ||
}, | ||
"scripts": { | ||
"istanbul": "istanbul cover _mocha --report lcovonly -- -R spec test/", | ||
"lint": "eslint index.js lib test", | ||
"lint": "standard", | ||
"test": "mocha -R spec test/", | ||
"test:watch": "nodemon --watch lib --watch test --watch index.js --exec 'npm test'", | ||
"start": "npm run test:watch", | ||
"toc": "./node_modules/.bin/doctoc ." | ||
"toc": "doctoc ." | ||
} | ||
} |
206
README.md
# math-codegen | ||
[![Build Status][travis-image]][travis-url] | ||
[![NPM][npm-image]][npm-url] | ||
[![Build Status][travis-image]][travis-url] | ||
[![Coverage Status][coveralls-image]][coveralls-url] | ||
[![Dependency Status][david-image]][david-url] | ||
[![unstable](http://badges.github.io/stability-badges/dist/unstable.svg)](http://github.com/badges/stability-badges) | ||
[![Stability](https://img.shields.io/badge/stability-stable-green.svg)]() | ||
[![js-standard-style](https://cdn.rawgit.com/feross/standard/master/badge.svg)](https://github.com/feross/standard) | ||
> Generates JavaScript code from mathematical expressions | ||
@@ -14,3 +14,3 @@ | ||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> | ||
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* | ||
**Table of Contents** * generated with [DocToc](https://github.com/thlorenz/doctoc) * | ||
@@ -23,2 +23,3 @@ - [Description](#description) | ||
- [Differences with math.js expression parser](#differences-with-mathjs-expression-parser) | ||
- [Operators](#operators) | ||
- [Install](#install) | ||
@@ -42,3 +43,3 @@ - [Usage](#usage) | ||
A flexible interpreter for mathematical expressions which allows the programmer to change the usual semantic of an | ||
An interpreter for mathematical expressions which allows the programmer to change the usual semantic of an | ||
operator bringing the operator overloading polymorphism to JavaScript (emulated with function calls), | ||
@@ -51,9 +52,12 @@ in addition an expression can be evaluated under any adapted namespace providing expression portability between numeric libraries | ||
- `parse` a mathematical expression is parsed with [Esprima](http://esprima.org/) | ||
- `compile` the parsed string is compiled against a namespace producing executable JavaScript code | ||
- `eval` the executable JavaScript code is evaluated against a context | ||
- `parse`: a mathematical expression is parsed with [`mr-parse`](https://github.com/maurizzzio/mr-parser), in the ideal scenario | ||
it would use [math.js expression parser](http://mathjs.org/docs/expressions/index.html) however it's not modularized yet | ||
and including all math.js is just an overkill, probably `mr-parse` will be replaced with math.js expression parser when | ||
it reaches npm as a module :) | ||
- `compile`: the parsed string is compiled against a namespace producing executable JavaScript code | ||
- `eval`: the executable JavaScript code is evaluated against a context | ||
#### Parse | ||
For example let's consider the following expression with the variable `x` which is user defined: | ||
For example let's consider the following expression with the variable `x` which is defined by the user: | ||
@@ -64,7 +68,7 @@ ```javascript | ||
the expression can be emulated with function calls instead of operators (the parser identifies the addition and multiplication | ||
expression as [Binary Expressions](https://github.com/estree/estree/blob/master/spec.md#binaryexpression)) | ||
the expression can be emulated with function calls instead of operators, math-codegen will map many mathematical | ||
operators to callable methods | ||
```javascript | ||
'add(1, multiply(2, x))' | ||
'add(1, mul(2, x))' | ||
``` | ||
@@ -75,10 +79,10 @@ | ||
```javascript | ||
'ns.add(1, ns.multiply(2, x))' | ||
'ns.add(1, ns.mul(2, x))' | ||
``` | ||
the variables (which for the parser are [Identifiers](https://github.com/estree/estree/blob/master/spec.md#identifier)) | ||
the variables (which for the parser are `symbols` | ||
come from a context called `scope` but they might also be constant values defined in the namespace: | ||
```javascript | ||
'ns.add(1, ns.multiply(2, (scope["x"] || ns["x"]) ))' | ||
'ns.add(1, ns.mul(2, (scope["x"] || ns["x"]) ))' | ||
``` | ||
@@ -90,3 +94,3 @@ | ||
```javascript | ||
'ns.add(ns.factory(1), ns.multiply(ns.factory(2), (scope["x"] || ns["x"]) ))' | ||
'ns.add(ns.factory(1), ns.mul(ns.factory(2), (scope["x"] || ns["x"]) ))' | ||
``` | ||
@@ -103,3 +107,4 @@ | ||
// returns something like this | ||
(function (ns) { | ||
(function (definitions) { | ||
var ns = definitions.namespace | ||
return { | ||
@@ -110,5 +115,6 @@ eval: function (scope) { | ||
// the string parsed above goes here | ||
return ns.add(ns.factory(1), ns.mul(ns.factory(2), (scope["x"] || ns["x"]) )) | ||
} | ||
} | ||
})(namespace) | ||
})(definitions) // definitions created by math-codegen | ||
``` | ||
@@ -129,5 +135,2 @@ | ||
- `math.js` has a custom expression parser (which means it has additional types of nodes), | ||
`math-codegen` uses Esprima which support the ES5 grammar only | ||
[(ESTree AST nodes)](https://github.com/estree/estree/blob/master/spec.md) | ||
- `math.js` v1.x arrays can represent matrices with `ns.matrix` or as a raw arrays, `math-codegen` doesn't | ||
@@ -137,2 +140,45 @@ make any assumptions of the arrays and treats them just like any other literal allowing the namespace to | ||
### Operators | ||
The following operators recognized by `mr-parser` are named as follows when compiled | ||
```javascript | ||
'+': 'add' | ||
'-': 'sub' | ||
'*': 'mul' | ||
'/': 'div' | ||
'^': 'pow' | ||
'%': 'mod' | ||
'!': 'factorial' | ||
// misc operators | ||
'|': 'bitwiseOR' | ||
'^|': 'bitwiseXOR' | ||
'&': 'bitwiseAND' | ||
'||': 'logicalOR' | ||
'xor': 'logicalXOR' | ||
'&&': 'logicalAND' | ||
// comparison | ||
'<': 'lessThan' | ||
'>': 'greaterThan' | ||
'<=': 'lessEqualThan' | ||
'>=': 'greaterEqualThan' | ||
'===': 'strictlyEqual' | ||
'==': 'equal' | ||
'!==': 'strictlyNotEqual' | ||
'!=': 'notEqual' | ||
// shift | ||
'>>': 'shiftRight' | ||
'<<': 'shiftLeft' | ||
'>>>': 'unsignedRightShift' | ||
// unary | ||
'+': 'positive' | ||
'-': 'negative' | ||
'~': 'oneComplement' | ||
``` | ||
## Install | ||
@@ -163,4 +209,4 @@ | ||
* `[options.factory="ns.factory"]` {string} factory method under the namespace | ||
* `[options.raw=false]` {boolean} True to interpret BinaryExpression, UnaryExpression and ArrayExpression | ||
in a raw way without wrapping the operators with identifiers, e.g. `-1` will be compiled as | ||
* `[options.raw=false]` {boolean} True to interpret OperatorNode, UnaryNode and ArrayNode | ||
in a raw way without wrapping the operators with identifiers e.g. `-1` will be compiled as | ||
`-1` instead of `ns.negative(ns.factory(1))` | ||
@@ -177,28 +223,6 @@ * `[options.rawArrayExpressionElements=true]` {boolean} true to interpret the array elements in a raw way | ||
Parses a program using [Esprima](http://esprima.org/), each Expression Statement is saved in | ||
Parses a program using [`mr-parse`](https://github.com/maurizzzio/mr-parser), each Expression Statement is saved in | ||
`instance.statements` | ||
Node types implemented: | ||
- Nodes: | ||
- [ExpressionStatement](https://github.com/estree/estree/blob/master/spec.md#expressionstatement) | ||
- Expressions: | ||
- [ArrayExpression](https://github.com/estree/estree/blob/master/spec.md#arrayexpression) | ||
- [UnaryExpression](https://github.com/estree/estree/blob/master/spec.md#unaryexpression) | ||
available operators emulated with function calls can be found | ||
[here](https://github.com/maurizzzio/math-codegen/blob/master/lib/misc/UnaryOperator.js) | ||
- [BinaryExpression](https://github.com/estree/estree/blob/master/spec.md#binaryexpression) | ||
available operators emulated with function calls can be found | ||
[here](https://github.com/maurizzzio/math-codegen/blob/master/lib/misc/BinaryOperator.js) | ||
- [AssignmentExpression](https://github.com/estree/estree/blob/master/spec.md#assignmentexpression) | ||
- [ConditionalExpression](https://github.com/estree/estree/blob/master/spec.md#conditionalexpression) | ||
- [CallExpression](https://github.com/estree/estree/blob/master/spec.md#callexpression) | ||
- Misc: | ||
- [Identifiers](https://github.com/estree/estree/blob/master/spec.md#identifier), identifier | ||
resolution follows this order: | ||
- namespace | ||
- scope | ||
- definitions stored in `instance.defs` | ||
- [Literals](https://github.com/estree/estree/blob/master/spec.md#literal) | ||
The documentation for the available nodes is described in [`mr-parse`](https://github.com/maurizzzio/mr-parser) | ||
@@ -211,5 +235,7 @@ ### `instance.compile(namespace)` | ||
Compiles the code making `namespace`'s properties available during evaluation | ||
Compiles the code making `namespace`'s properties available during evaluation, **it's required | ||
to have the `factory` property defined** | ||
**returns** {Object} | ||
* `return.code` {string} the body of the function to be evaluated with `eval` | ||
* `return.eval` {Function} Function to be evaluated under a context | ||
@@ -232,20 +258,17 @@ **params** | ||
```javascript | ||
'use strict' | ||
var CodeGenerator = require('math-codegen') | ||
var numeric = { | ||
factory: function (a) { | ||
// anything is a number :) | ||
return Number(a); | ||
}, | ||
add: function (a, b) { | ||
return a + b; | ||
}, | ||
mul: function (a, b) { | ||
return a * b; | ||
} | ||
}; | ||
factory: function (a) { return a }, | ||
add: function (a, b) { return a + b }, | ||
mul: function (a, b) { return a * b } | ||
} | ||
var instance = new CodeGenerator() | ||
// 1 + 2 * 3 = 7 | ||
new CodeGenerator() | ||
.parse('1 + 2 * x') | ||
.compile(numeric); | ||
instance.eval({x : 3}); // 1 + 2 * 3 = 7 | ||
.compile(numeric) | ||
.eval({x: 3}) | ||
) | ||
``` | ||
@@ -256,3 +279,4 @@ | ||
```javascript | ||
var instance = new CodeGenerator(); | ||
'use strict' | ||
var CodeGenerator = require('math-codegen') | ||
@@ -263,18 +287,20 @@ var imaginary = { | ||
if (typeof a === 'number') { | ||
return [a, 0]; | ||
return [a, 0] | ||
} | ||
return [a[0] || 0, a[1] || 0]; | ||
return [a[0] || 0, a[1] || 0] | ||
}, | ||
add: function (a, b) { | ||
var re = a[0] + b[0]; | ||
var im = a[1] + b[1]; | ||
return [re, im]; | ||
var re = a[0] + b[0] | ||
var im = a[1] + b[1] | ||
return [re, im] | ||
}, | ||
mul: function (a, b) { | ||
var re = a[0] * b[0] - a[1] * b[1]; | ||
var im = a[0] * b[1] + a[1] * b[0]; | ||
return [re, im]; | ||
var re = a[0] * b[0] - a[1] * b[1] | ||
var im = a[0] * b[1] + a[1] * b[0] | ||
return [re, im] | ||
} | ||
}; | ||
} | ||
var instance = new CodeGenerator() | ||
// [1, 0] + [2, 0] * [1, 1] | ||
@@ -301,2 +327,5 @@ // [1, 0] + [2, 2] | ||
```javascript | ||
'use strict' | ||
var CodeGenerator = require('math-codegen') | ||
var interval = { | ||
@@ -306,25 +335,27 @@ factory: function (a) { | ||
if (typeof a === 'number') { | ||
return [a, a]; | ||
return [a, a] | ||
} | ||
return a; | ||
return [a[0], a[1]] | ||
}, | ||
add: function (x, y) { | ||
return [x[0] + y[0], x[1] + y[1]]; | ||
return [x[0] + y[0], x[1] + y[1]] | ||
}, | ||
mul: function (x, y) { | ||
var ac = x[0] * y[0]; | ||
var ad = x[0] * y[1]; | ||
var bc = x[1] * y[0]; | ||
var bd = x[1] * y[1]; | ||
return [Math.min(ac, ad, bc, bd), Math.max(ac, ad, bc, bd)]; | ||
var ac = x[0] * y[0] | ||
var ad = x[0] * y[1] | ||
var bc = x[1] * y[0] | ||
var bd = x[1] * y[1] | ||
return [Math.min(ac, ad, bc, bd), Math.max(ac, ad, bc, bd)] | ||
} | ||
}; | ||
} | ||
var instance = new CodeGenerator() | ||
// [1, 1] + [2, 2] * [-1, 2] | ||
// [1, 1] + [-2, 4] | ||
// [-1, 5] | ||
var instance = new CodeGenerator() | ||
instance | ||
.parse('1 + 2 * x') | ||
.compile(interval) | ||
.eval({x : [-1, 2]}); | ||
.eval({x: [-1, 2]}) | ||
``` | ||
@@ -335,2 +366,3 @@ | ||
- [math.js expression parser](http://mathjs.org/docs/expressions/index.html) | ||
- [angular v1.x parser](https://github.com/angular/angular.js/blob/master/src/ng/parse.js) | ||
@@ -341,3 +373,3 @@ ## License | ||
[npm-image]: https://nodei.co/npm/math-codegen.png?downloads=true | ||
[npm-image]: https://img.shields.io/npm/v/math-codegen.svg?style=flat | ||
[npm-url]: https://npmjs.org/package/math-codegen | ||
@@ -348,3 +380,1 @@ [travis-image]: https://travis-ci.org/maurizzzio/math-codegen.svg?branch=master | ||
[coveralls-url]: https://coveralls.io/r/maurizzzio/math-codegen?branch=master | ||
[david-image]: https://david-dm.org/maurizzzio/math-codegen.svg | ||
[david-url]: https://david-dm.org/maurizzzio/math-codegen |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
0
359
19745
16
262
2
+ Addedmr-parser@^0.1.0
+ Addedmr-parser@0.1.0(transitive)
- Removedesprima@^2.2.0
- Removedesprima@2.7.3(transitive)