diff --git a/cases/24-define-function/src/index.heta b/cases/24-define-function/src/index.heta index 736b4698..f1ab557f 100644 --- a/cases/24-define-function/src/index.heta +++ b/cases/24-define-function/src/index.heta @@ -23,11 +23,11 @@ math: f4(x)^2 }; +/* // throws error #defineFunction f6 { }; -/* // ref to wrong function #defineFunction f7 { arguments: [x,y], @@ -68,7 +68,7 @@ c1 @Compartment .= 2; s1 @Species {compartment: c1} .= 100; -r1 @Reaction {actors: s1=} := k1 * pow7(1.1) * c1; +r1 @Reaction {actors: s1=} := k1 * f5(1.1, k1) * pow(1.1, 2) * c1; k1 @Const = 0.1; diff --git a/src/core/expression.js b/src/core/expression.js index 3abb70aa..af3aab3f 100644 --- a/src/core/expression.js +++ b/src/core/expression.js @@ -69,10 +69,24 @@ class Expression { let clonedMath = this.exprParsed.cloneDeep(); let expr = new Expression(clonedMath); expr._logger = this._logger; + return expr; + } + // substitute user defined functions by their content, return Expression + substituteByDefinitions() { + let transformed = this.exprParsed.transform((node) => { + if (node.type === 'FunctionNode' && node.fnObj && !node.fnObj.isCore) { + return node.fnObj.substitute(node.args); + } else { + return node; + } + }); + + let expr = new Expression(transformed); + expr._logger = this._logger; return expr; } - updateReferences(q = {}){ + updateReferences(q = {}) { this.exprParsed.traverse((node , path/*, parent*/) => { if (node.type === 'SymbolNode' && path !== 'fn') { // transform only SymbolNode let oldRef = node.name; diff --git a/src/core/function-def.js b/src/core/function-def.js index 1df1d584..3f0eab69 100644 --- a/src/core/function-def.js +++ b/src/core/function-def.js @@ -95,6 +95,27 @@ class FunctionDef extends Top { static get validate() { return ajv.compile(schema); } + // get Node (from mathjs) by substitution + substitute(nodes = []) { + // check arguments + if (this.arguments.length > nodes.length) { + throw new TypeError(`Function "${this.id}" requires minimum ${this.arguments.length} arguments, got ${nodes.length}`); + } + + // substitute arguments by nodes + let transformed = this.math.exprParsed.transform((node) => { + let argIndex = this.arguments.indexOf(node.name); + if (node.type === 'SymbolNode' && argIndex !== -1) { + return nodes[argIndex]; + } else if (node.type === 'FunctionNode' && node.fnObj && !node.fnObj.isCore) { + return node.fnObj.substitute(node.args); + } else { + return node; + } + }); + + return transformed; + } bind() { // super.bind(); let {logger, functionDefStorage} = this._container; diff --git a/src/dbsolve-export/expression.js b/src/dbsolve-export/expression.js index 611346ad..b428a090 100644 --- a/src/dbsolve-export/expression.js +++ b/src/dbsolve-export/expression.js @@ -1,14 +1,20 @@ const { Expression } = require('../core/expression'); -Expression.prototype.toSLVString = function(powTransform = 'keep') { +Expression.prototype.toSLVString = function(powTransform = 'keep', substituteByDefinitions = true) { + let tree = substituteByDefinitions ? this.substituteByDefinitions().exprParsed : this.exprParsed; + if (['keep', 'operator', 'function'].indexOf(powTransform) === -1) { throw new TypeError('powTransform must be one of values: "keep", "operator", "function".'); } let SLVStringHandler = (node, options) => { + + // OperatorNode if (node.type==='OperatorNode' && node.fn==='pow' && powTransform==='function') { return `pow(${node.args[0].toString(options)}, ${node.args[1].toString(options)})`; } + + // FunctionNode if (node.type==='FunctionNode' && node.fn.name==='pow' && powTransform==='operator') { if (node.args[0].type==='OperatorNode') { var arg0 = `(${node.args[0].toString(options)})`; @@ -132,7 +138,6 @@ Expression.prototype.toSLVString = function(powTransform = 'keep') { .map((arg) => arg.toString(options)); return `log(${args[0]}) / log(2)`; } - // piecewise function if (node.type === 'FunctionNode' && node.fn.name === 'piecewise') { let msg = `DBS and SLV formats do not support "piecewise" function, got "${node.toString()}"`; this._logger.error(msg); @@ -140,7 +145,8 @@ Expression.prototype.toSLVString = function(powTransform = 'keep') { .map((arg) => arg.toString(options)); return `piecewise(${args.join(',')})`; } - // ternary operator + + // ConditionalNode: ternary operator if (node.type === 'ConditionalNode') { let condition = _removeParenthesis(node.condition); let trueExpr = node.trueExpr.toString(options); @@ -181,12 +187,11 @@ Expression.prototype.toSLVString = function(powTransform = 'keep') { } }; - return this.exprParsed - .toString({ - parenthesis: 'keep', - implicit: 'show', - handler: SLVStringHandler - }); + return tree.toString({ + parenthesis: 'keep', + implicit: 'show', + handler: SLVStringHandler + }); }; /* remove parenthesis from top */ diff --git a/src/dbsolve-export/index.js b/src/dbsolve-export/index.js index aaf73a4a..5165848c 100644 --- a/src/dbsolve-export/index.js +++ b/src/dbsolve-export/index.js @@ -47,13 +47,6 @@ class DBSolveExport extends AbstractExport{ makeText() { let logger = this._container.logger; - // display that function definition is not supported - let userDefinedFunctions = [...this._container.functionDefStorage] - .filter(([id, functionDef]) => !functionDef.isCore) - .map(([id, functionDef]) => id); - if (userDefinedFunctions.length > 0) { - logger.warn(`User defined functions: ${userDefinedFunctions.join(', ')} are presented in platform but not supported by DBSolve export.`); - } let selectedNamespaces = this.selectedNamespaces();