378 lines
12 KiB
JavaScript
378 lines
12 KiB
JavaScript
"use strict";
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
exports.createNode = void 0;
|
|
var _is = require("../../utils/is.js");
|
|
var _keywords = require("../keywords.js");
|
|
var _object = require("../../utils/object.js");
|
|
var _factory = require("../../utils/factory.js");
|
|
var _map = require("../../utils/map.js");
|
|
const name = 'Node';
|
|
const dependencies = ['mathWithTransform'];
|
|
const createNode = exports.createNode = /* #__PURE__ */(0, _factory.factory)(name, dependencies, _ref => {
|
|
let {
|
|
mathWithTransform
|
|
} = _ref;
|
|
/**
|
|
* Validate the symbol names of a scope.
|
|
* Throws an error when the scope contains an illegal symbol.
|
|
* @param {Object} scope
|
|
*/
|
|
function _validateScope(scope) {
|
|
for (const symbol of [..._keywords.keywords]) {
|
|
if (scope.has(symbol)) {
|
|
throw new Error('Scope contains an illegal symbol, "' + symbol + '" is a reserved keyword');
|
|
}
|
|
}
|
|
}
|
|
class Node {
|
|
get type() {
|
|
return 'Node';
|
|
}
|
|
get isNode() {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Evaluate the node
|
|
* @param {Object} [scope] Scope to read/write variables
|
|
* @return {*} Returns the result
|
|
*/
|
|
evaluate(scope) {
|
|
return this.compile().evaluate(scope);
|
|
}
|
|
|
|
/**
|
|
* Compile the node into an optimized, evauatable JavaScript function
|
|
* @return {{evaluate: function([Object])}} object
|
|
* Returns an object with a function 'evaluate',
|
|
* which can be invoked as expr.evaluate([scope: Object]),
|
|
* where scope is an optional object with
|
|
* variables.
|
|
*/
|
|
compile() {
|
|
const expr = this._compile(mathWithTransform, {});
|
|
const args = {};
|
|
const context = null;
|
|
function evaluate(scope) {
|
|
const s = (0, _map.createMap)(scope);
|
|
_validateScope(s);
|
|
return expr(s, args, context);
|
|
}
|
|
return {
|
|
evaluate
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Compile a node into a JavaScript function.
|
|
* This basically pre-calculates as much as possible and only leaves open
|
|
* calculations which depend on a dynamic scope with variables.
|
|
* @param {Object} math Math.js namespace with functions and constants.
|
|
* @param {Object} argNames An object with argument names as key and `true`
|
|
* as value. Used in the SymbolNode to optimize
|
|
* for arguments from user assigned functions
|
|
* (see FunctionAssignmentNode) or special symbols
|
|
* like `end` (see IndexNode).
|
|
* @return {function} Returns a function which can be called like:
|
|
* evalNode(scope: Object, args: Object, context: *)
|
|
*/
|
|
_compile(math, argNames) {
|
|
throw new Error('Method _compile must be implemented by type ' + this.type);
|
|
}
|
|
|
|
/**
|
|
* Execute a callback for each of the child nodes of this node
|
|
* @param {function(child: Node, path: string, parent: Node)} callback
|
|
*/
|
|
forEach(callback) {
|
|
// must be implemented by each of the Node implementations
|
|
throw new Error('Cannot run forEach on a Node interface');
|
|
}
|
|
|
|
/**
|
|
* Create a new Node whose children are the results of calling the
|
|
* provided callback function for each child of the original node.
|
|
* @param {function(child: Node, path: string, parent: Node): Node} callback
|
|
* @returns {OperatorNode} Returns a transformed copy of the node
|
|
*/
|
|
map(callback) {
|
|
// must be implemented by each of the Node implementations
|
|
throw new Error('Cannot run map on a Node interface');
|
|
}
|
|
|
|
/**
|
|
* Validate whether an object is a Node, for use with map
|
|
* @param {Node} node
|
|
* @returns {Node} Returns the input if it's a node, else throws an Error
|
|
* @protected
|
|
*/
|
|
_ifNode(node) {
|
|
if (!(0, _is.isNode)(node)) {
|
|
throw new TypeError('Callback function must return a Node');
|
|
}
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* Recursively traverse all nodes in a node tree. Executes given callback for
|
|
* this node and each of its child nodes.
|
|
* @param {function(node: Node, path: string, parent: Node)} callback
|
|
* A callback called for every node in the node tree.
|
|
*/
|
|
traverse(callback) {
|
|
// execute callback for itself
|
|
// eslint-disable-next-line
|
|
callback(this, null, null);
|
|
|
|
// recursively traverse over all children of a node
|
|
function _traverse(node, callback) {
|
|
node.forEach(function (child, path, parent) {
|
|
callback(child, path, parent);
|
|
_traverse(child, callback);
|
|
});
|
|
}
|
|
_traverse(this, callback);
|
|
}
|
|
|
|
/**
|
|
* Recursively transform a node tree via a transform function.
|
|
*
|
|
* For example, to replace all nodes of type SymbolNode having name 'x' with
|
|
* a ConstantNode with value 2:
|
|
*
|
|
* const res = Node.transform(function (node, path, parent) {
|
|
* if (node && node.isSymbolNode) && (node.name === 'x')) {
|
|
* return new ConstantNode(2)
|
|
* }
|
|
* else {
|
|
* return node
|
|
* }
|
|
* })
|
|
*
|
|
* @param {function(node: Node, path: string, parent: Node) : Node} callback
|
|
* A mapping function accepting a node, and returning
|
|
* a replacement for the node or the original node. The "signature"
|
|
* of the callback must be:
|
|
* callback(node: Node, index: string, parent: Node) : Node
|
|
* @return {Node} Returns the original node or its replacement
|
|
*/
|
|
transform(callback) {
|
|
function _transform(child, path, parent) {
|
|
const replacement = callback(child, path, parent);
|
|
if (replacement !== child) {
|
|
// stop iterating when the node is replaced
|
|
return replacement;
|
|
}
|
|
return child.map(_transform);
|
|
}
|
|
return _transform(this, null, null);
|
|
}
|
|
|
|
/**
|
|
* Find any node in the node tree matching given filter function. For
|
|
* example, to find all nodes of type SymbolNode having name 'x':
|
|
*
|
|
* const results = Node.filter(function (node) {
|
|
* return (node && node.isSymbolNode) && (node.name === 'x')
|
|
* })
|
|
*
|
|
* @param {function(node: Node, path: string, parent: Node) : Node} callback
|
|
* A test function returning true when a node matches, and false
|
|
* otherwise. Function signature:
|
|
* callback(node: Node, index: string, parent: Node) : boolean
|
|
* @return {Node[]} nodes
|
|
* An array with nodes matching given filter criteria
|
|
*/
|
|
filter(callback) {
|
|
const nodes = [];
|
|
this.traverse(function (node, path, parent) {
|
|
if (callback(node, path, parent)) {
|
|
nodes.push(node);
|
|
}
|
|
});
|
|
return nodes;
|
|
}
|
|
|
|
/**
|
|
* Create a shallow clone of this node
|
|
* @return {Node}
|
|
*/
|
|
clone() {
|
|
// must be implemented by each of the Node implementations
|
|
throw new Error('Cannot clone a Node interface');
|
|
}
|
|
|
|
/**
|
|
* Create a deep clone of this node
|
|
* @return {Node}
|
|
*/
|
|
cloneDeep() {
|
|
return this.map(function (node) {
|
|
return node.cloneDeep();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Deep compare this node with another node.
|
|
* @param {Node} other
|
|
* @return {boolean} Returns true when both nodes are of the same type and
|
|
* contain the same values (as do their childs)
|
|
*/
|
|
equals(other) {
|
|
return other ? this.type === other.type && (0, _object.deepStrictEqual)(this, other) : false;
|
|
}
|
|
|
|
/**
|
|
* Get string representation. (wrapper function)
|
|
*
|
|
* This function can get an object of the following form:
|
|
* {
|
|
* handler: //This can be a callback function of the form
|
|
* // "function callback(node, options)"or
|
|
* // a map that maps function names (used in FunctionNodes)
|
|
* // to callbacks
|
|
* parenthesis: "keep" //the parenthesis option (This is optional)
|
|
* }
|
|
*
|
|
* @param {Object} [options]
|
|
* @return {string}
|
|
*/
|
|
toString(options) {
|
|
const customString = this._getCustomString(options);
|
|
if (typeof customString !== 'undefined') {
|
|
return customString;
|
|
}
|
|
return this._toString(options);
|
|
}
|
|
|
|
/**
|
|
* Internal function to generate the string output.
|
|
* This has to be implemented by every Node
|
|
*
|
|
* @throws {Error}
|
|
*/
|
|
_toString() {
|
|
// must be implemented by each of the Node implementations
|
|
throw new Error('_toString not implemented for ' + this.type);
|
|
}
|
|
|
|
/**
|
|
* Get a JSON representation of the node
|
|
* Both .toJSON() and the static .fromJSON(json) should be implemented by all
|
|
* implementations of Node
|
|
* @returns {Object}
|
|
*/
|
|
toJSON() {
|
|
throw new Error('Cannot serialize object: toJSON not implemented by ' + this.type);
|
|
}
|
|
|
|
/**
|
|
* Get HTML representation. (wrapper function)
|
|
*
|
|
* This function can get an object of the following form:
|
|
* {
|
|
* handler: //This can be a callback function of the form
|
|
* // "function callback(node, options)" or
|
|
* // a map that maps function names (used in FunctionNodes)
|
|
* // to callbacks
|
|
* parenthesis: "keep" //the parenthesis option (This is optional)
|
|
* }
|
|
*
|
|
* @param {Object} [options]
|
|
* @return {string}
|
|
*/
|
|
toHTML(options) {
|
|
const customString = this._getCustomString(options);
|
|
if (typeof customString !== 'undefined') {
|
|
return customString;
|
|
}
|
|
return this._toHTML(options);
|
|
}
|
|
|
|
/**
|
|
* Internal function to generate the HTML output.
|
|
* This has to be implemented by every Node
|
|
*
|
|
* @throws {Error}
|
|
*/
|
|
_toHTML() {
|
|
// must be implemented by each of the Node implementations
|
|
throw new Error('_toHTML not implemented for ' + this.type);
|
|
}
|
|
|
|
/**
|
|
* Get LaTeX representation. (wrapper function)
|
|
*
|
|
* This function can get an object of the following form:
|
|
* {
|
|
* handler: //This can be a callback function of the form
|
|
* // "function callback(node, options)"or
|
|
* // a map that maps function names (used in FunctionNodes)
|
|
* // to callbacks
|
|
* parenthesis: "keep" //the parenthesis option (This is optional)
|
|
* }
|
|
*
|
|
* @param {Object} [options]
|
|
* @return {string}
|
|
*/
|
|
toTex(options) {
|
|
const customString = this._getCustomString(options);
|
|
if (typeof customString !== 'undefined') {
|
|
return customString;
|
|
}
|
|
return this._toTex(options);
|
|
}
|
|
|
|
/**
|
|
* Internal function to generate the LaTeX output.
|
|
* This has to be implemented by every Node
|
|
*
|
|
* @param {Object} [options]
|
|
* @throws {Error}
|
|
*/
|
|
_toTex(options) {
|
|
// must be implemented by each of the Node implementations
|
|
throw new Error('_toTex not implemented for ' + this.type);
|
|
}
|
|
|
|
/**
|
|
* Helper used by `to...` functions.
|
|
*/
|
|
_getCustomString(options) {
|
|
if (options && typeof options === 'object') {
|
|
switch (typeof options.handler) {
|
|
case 'object':
|
|
case 'undefined':
|
|
return;
|
|
case 'function':
|
|
return options.handler(this, options);
|
|
default:
|
|
throw new TypeError('Object or function expected as callback');
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get identifier.
|
|
* @return {string}
|
|
*/
|
|
getIdentifier() {
|
|
return this.type;
|
|
}
|
|
|
|
/**
|
|
* Get the content of the current Node.
|
|
* @return {Node} node
|
|
**/
|
|
getContent() {
|
|
return this;
|
|
}
|
|
}
|
|
return Node;
|
|
}, {
|
|
isClass: true,
|
|
isNode: true
|
|
}); |