ConditionalNode.js 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. import _defineProperty from "@babel/runtime/helpers/defineProperty";
  2. import { isBigNumber, isComplex, isNode, isUnit, typeOf } from '../../utils/is.js';
  3. import { factory } from '../../utils/factory.js';
  4. import { getPrecedence } from '../operators.js';
  5. var name = 'ConditionalNode';
  6. var dependencies = ['Node'];
  7. export var createConditionalNode = /* #__PURE__ */factory(name, dependencies, _ref => {
  8. var {
  9. Node
  10. } = _ref;
  11. /**
  12. * Test whether a condition is met
  13. * @param {*} condition
  14. * @returns {boolean} true if condition is true or non-zero, else false
  15. */
  16. function testCondition(condition) {
  17. if (typeof condition === 'number' || typeof condition === 'boolean' || typeof condition === 'string') {
  18. return !!condition;
  19. }
  20. if (condition) {
  21. if (isBigNumber(condition)) {
  22. return !condition.isZero();
  23. }
  24. if (isComplex(condition)) {
  25. return !!(condition.re || condition.im);
  26. }
  27. if (isUnit(condition)) {
  28. return !!condition.value;
  29. }
  30. }
  31. if (condition === null || condition === undefined) {
  32. return false;
  33. }
  34. throw new TypeError('Unsupported type of condition "' + typeOf(condition) + '"');
  35. }
  36. class ConditionalNode extends Node {
  37. /**
  38. * A lazy evaluating conditional operator: 'condition ? trueExpr : falseExpr'
  39. *
  40. * @param {Node} condition Condition, must result in a boolean
  41. * @param {Node} trueExpr Expression evaluated when condition is true
  42. * @param {Node} falseExpr Expression evaluated when condition is true
  43. *
  44. * @constructor ConditionalNode
  45. * @extends {Node}
  46. */
  47. constructor(condition, trueExpr, falseExpr) {
  48. super();
  49. if (!isNode(condition)) {
  50. throw new TypeError('Parameter condition must be a Node');
  51. }
  52. if (!isNode(trueExpr)) {
  53. throw new TypeError('Parameter trueExpr must be a Node');
  54. }
  55. if (!isNode(falseExpr)) {
  56. throw new TypeError('Parameter falseExpr must be a Node');
  57. }
  58. this.condition = condition;
  59. this.trueExpr = trueExpr;
  60. this.falseExpr = falseExpr;
  61. }
  62. get type() {
  63. return name;
  64. }
  65. get isConditionalNode() {
  66. return true;
  67. }
  68. /**
  69. * Compile a node into a JavaScript function.
  70. * This basically pre-calculates as much as possible and only leaves open
  71. * calculations which depend on a dynamic scope with variables.
  72. * @param {Object} math Math.js namespace with functions and constants.
  73. * @param {Object} argNames An object with argument names as key and `true`
  74. * as value. Used in the SymbolNode to optimize
  75. * for arguments from user assigned functions
  76. * (see FunctionAssignmentNode) or special symbols
  77. * like `end` (see IndexNode).
  78. * @return {function} Returns a function which can be called like:
  79. * evalNode(scope: Object, args: Object, context: *)
  80. */
  81. _compile(math, argNames) {
  82. var evalCondition = this.condition._compile(math, argNames);
  83. var evalTrueExpr = this.trueExpr._compile(math, argNames);
  84. var evalFalseExpr = this.falseExpr._compile(math, argNames);
  85. return function evalConditionalNode(scope, args, context) {
  86. return testCondition(evalCondition(scope, args, context)) ? evalTrueExpr(scope, args, context) : evalFalseExpr(scope, args, context);
  87. };
  88. }
  89. /**
  90. * Execute a callback for each of the child nodes of this node
  91. * @param {function(child: Node, path: string, parent: Node)} callback
  92. */
  93. forEach(callback) {
  94. callback(this.condition, 'condition', this);
  95. callback(this.trueExpr, 'trueExpr', this);
  96. callback(this.falseExpr, 'falseExpr', this);
  97. }
  98. /**
  99. * Create a new ConditionalNode whose children are the results of calling
  100. * the provided callback function for each child of the original node.
  101. * @param {function(child: Node, path: string, parent: Node): Node} callback
  102. * @returns {ConditionalNode} Returns a transformed copy of the node
  103. */
  104. map(callback) {
  105. return new ConditionalNode(this._ifNode(callback(this.condition, 'condition', this)), this._ifNode(callback(this.trueExpr, 'trueExpr', this)), this._ifNode(callback(this.falseExpr, 'falseExpr', this)));
  106. }
  107. /**
  108. * Create a clone of this node, a shallow copy
  109. * @return {ConditionalNode}
  110. */
  111. clone() {
  112. return new ConditionalNode(this.condition, this.trueExpr, this.falseExpr);
  113. }
  114. /**
  115. * Get string representation
  116. * @param {Object} options
  117. * @return {string} str
  118. */
  119. _toString(options) {
  120. var parenthesis = options && options.parenthesis ? options.parenthesis : 'keep';
  121. var precedence = getPrecedence(this, parenthesis, options && options.implicit);
  122. // Enclose Arguments in parentheses if they are an OperatorNode
  123. // or have lower or equal precedence
  124. // NOTE: enclosing all OperatorNodes in parentheses is a decision
  125. // purely based on aesthetics and readability
  126. var condition = this.condition.toString(options);
  127. var conditionPrecedence = getPrecedence(this.condition, parenthesis, options && options.implicit);
  128. if (parenthesis === 'all' || this.condition.type === 'OperatorNode' || conditionPrecedence !== null && conditionPrecedence <= precedence) {
  129. condition = '(' + condition + ')';
  130. }
  131. var trueExpr = this.trueExpr.toString(options);
  132. var truePrecedence = getPrecedence(this.trueExpr, parenthesis, options && options.implicit);
  133. if (parenthesis === 'all' || this.trueExpr.type === 'OperatorNode' || truePrecedence !== null && truePrecedence <= precedence) {
  134. trueExpr = '(' + trueExpr + ')';
  135. }
  136. var falseExpr = this.falseExpr.toString(options);
  137. var falsePrecedence = getPrecedence(this.falseExpr, parenthesis, options && options.implicit);
  138. if (parenthesis === 'all' || this.falseExpr.type === 'OperatorNode' || falsePrecedence !== null && falsePrecedence <= precedence) {
  139. falseExpr = '(' + falseExpr + ')';
  140. }
  141. return condition + ' ? ' + trueExpr + ' : ' + falseExpr;
  142. }
  143. /**
  144. * Get a JSON representation of the node
  145. * @returns {Object}
  146. */
  147. toJSON() {
  148. return {
  149. mathjs: name,
  150. condition: this.condition,
  151. trueExpr: this.trueExpr,
  152. falseExpr: this.falseExpr
  153. };
  154. }
  155. /**
  156. * Instantiate an ConditionalNode from its JSON representation
  157. * @param {Object} json
  158. * An object structured like
  159. * ```
  160. * {"mathjs": "ConditionalNode",
  161. * "condition": ...,
  162. * "trueExpr": ...,
  163. * "falseExpr": ...}
  164. * ```
  165. * where mathjs is optional
  166. * @returns {ConditionalNode}
  167. */
  168. static fromJSON(json) {
  169. return new ConditionalNode(json.condition, json.trueExpr, json.falseExpr);
  170. }
  171. /**
  172. * Get HTML representation
  173. * @param {Object} options
  174. * @return {string} str
  175. */
  176. toHTML(options) {
  177. var parenthesis = options && options.parenthesis ? options.parenthesis : 'keep';
  178. var precedence = getPrecedence(this, parenthesis, options && options.implicit);
  179. // Enclose Arguments in parentheses if they are an OperatorNode
  180. // or have lower or equal precedence
  181. // NOTE: enclosing all OperatorNodes in parentheses is a decision
  182. // purely based on aesthetics and readability
  183. var condition = this.condition.toHTML(options);
  184. var conditionPrecedence = getPrecedence(this.condition, parenthesis, options && options.implicit);
  185. if (parenthesis === 'all' || this.condition.type === 'OperatorNode' || conditionPrecedence !== null && conditionPrecedence <= precedence) {
  186. condition = '<span class="math-parenthesis math-round-parenthesis">(</span>' + condition + '<span class="math-parenthesis math-round-parenthesis">)</span>';
  187. }
  188. var trueExpr = this.trueExpr.toHTML(options);
  189. var truePrecedence = getPrecedence(this.trueExpr, parenthesis, options && options.implicit);
  190. if (parenthesis === 'all' || this.trueExpr.type === 'OperatorNode' || truePrecedence !== null && truePrecedence <= precedence) {
  191. trueExpr = '<span class="math-parenthesis math-round-parenthesis">(</span>' + trueExpr + '<span class="math-parenthesis math-round-parenthesis">)</span>';
  192. }
  193. var falseExpr = this.falseExpr.toHTML(options);
  194. var falsePrecedence = getPrecedence(this.falseExpr, parenthesis, options && options.implicit);
  195. if (parenthesis === 'all' || this.falseExpr.type === 'OperatorNode' || falsePrecedence !== null && falsePrecedence <= precedence) {
  196. falseExpr = '<span class="math-parenthesis math-round-parenthesis">(</span>' + falseExpr + '<span class="math-parenthesis math-round-parenthesis">)</span>';
  197. }
  198. return condition + '<span class="math-operator math-conditional-operator">?</span>' + trueExpr + '<span class="math-operator math-conditional-operator">:</span>' + falseExpr;
  199. }
  200. /**
  201. * Get LaTeX representation
  202. * @param {Object} options
  203. * @return {string} str
  204. */
  205. _toTex(options) {
  206. return '\\begin{cases} {' + this.trueExpr.toTex(options) + '}, &\\quad{\\text{if }\\;' + this.condition.toTex(options) + '}\\\\{' + this.falseExpr.toTex(options) + '}, &\\quad{\\text{otherwise}}\\end{cases}';
  207. }
  208. }
  209. _defineProperty(ConditionalNode, "name", name);
  210. return ConditionalNode;
  211. }, {
  212. isClass: true,
  213. isNode: true
  214. });