├── .babelrc ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── docs ├── index.html ├── index.js └── math-parser.js ├── index.js ├── lib ├── __test__ │ ├── __snapshots__ │ │ └── parser.test.js.snap │ ├── parser.test.js │ ├── print.test.js │ └── toTex.test.js ├── parse.js ├── print.js └── toTex.js ├── package.json ├── webpack.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"], 3 | "plugins": [ 4 | "transform-flow-strip-types", 5 | "transform-object-rest-spread" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | docs 2 | dist 3 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended", 4 | "plugin:flowtype/recommended" 5 | ], 6 | "plugins": [ 7 | "flowtype" 8 | ], 9 | "rules": { 10 | "no-constant-condition": 0, 11 | "indent": ["error", 4], 12 | "linebreak-style": ["error", "unix"], 13 | "quotes": ["error", "single"], 14 | "semi": ["error", "never"], 15 | "no-nested-ternary": "error", 16 | "space-infix-ops": "error" 17 | }, 18 | "env": { 19 | "browser": true, 20 | "es6": true, 21 | "node": true, 22 | "mocha": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .eslintcache 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | lib 2 | docs 3 | test 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "node" 4 | notifications: 5 | email: false 6 | script: 7 | - npm test -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Kevin Barabash 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # math-parser 2 | 3 | [![Build Status](https://travis-ci.org/semantic-math/math-parser.svg?branch=master)](https://travis-ci.org/semantic-math/math-parser) 4 | 5 | Parse math strings to an AST suitable for symbolic manipulation. 6 | 7 | ## Features 8 | 9 | - flattened add/sub operations 10 | - flattened mul/div operations 11 | - equations and inequalities 12 | - absolute value, e.g. `||x - y| - |y - z||` 13 | - functions, e.g. `f(x, y, g(u, v))` 14 | - multi-char identifiers, e.g. `atan2(dy, dx)` 15 | - implicit multiplication, e.g. `(x)(y)`, `2x`, `a b` 16 | 17 | ## Demo 18 | 19 | [demo](https://semantic-math.github.io/math-parser/) 20 | 21 | ## Getting Started 22 | 23 | - `yarn install` 24 | - `npm run build` 25 | - `node demo.js` 26 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 33 | math-parser demo 34 | 35 | 36 |
37 |
38 |

math-parser demo

39 | 40 |
41 | 42 |
43 |
44 | 47 |
48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /docs/index.js: -------------------------------------------------------------------------------- 1 | window.addEventListener('load', function() { 2 | 3 | const parse = module.exports.parse 4 | 5 | const params = {} 6 | 7 | location.search.slice(1).split('&').forEach(part => { 8 | const [key, value] = part.split('=') 9 | params[key] = decodeURIComponent(value) 10 | }) 11 | 12 | const input = document.getElementById('input') 13 | const output = document.getElementById('output') 14 | const permalink = document.getElementById('permalink') 15 | 16 | if ('math' in params) { 17 | input.value = params.math 18 | } 19 | 20 | const update = function() { 21 | try { 22 | const ast = parse(input.value) 23 | output.textContent = JSON.stringify(ast, null, 2) 24 | } catch (e) { 25 | output.textContent = e.message 26 | } 27 | } 28 | 29 | update() 30 | 31 | input.addEventListener('input', update) 32 | 33 | permalink.addEventListener('click', function() { 34 | const math = encodeURIComponent(input.value) 35 | window.location = `?math=${math}` 36 | }) 37 | }) 38 | -------------------------------------------------------------------------------- /docs/math-parser.js: -------------------------------------------------------------------------------- 1 | module.exports = 2 | /******/ (function(modules) { // webpackBootstrap 3 | /******/ // The module cache 4 | /******/ var installedModules = {}; 5 | 6 | /******/ // The require function 7 | /******/ function __webpack_require__(moduleId) { 8 | 9 | /******/ // Check if module is in cache 10 | /******/ if(installedModules[moduleId]) 11 | /******/ return installedModules[moduleId].exports; 12 | 13 | /******/ // Create a new module (and put it into the cache) 14 | /******/ var module = installedModules[moduleId] = { 15 | /******/ i: moduleId, 16 | /******/ l: false, 17 | /******/ exports: {} 18 | /******/ }; 19 | 20 | /******/ // Execute the module function 21 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 22 | 23 | /******/ // Flag the module as loaded 24 | /******/ module.l = true; 25 | 26 | /******/ // Return the exports of the module 27 | /******/ return module.exports; 28 | /******/ } 29 | 30 | 31 | /******/ // expose the modules object (__webpack_modules__) 32 | /******/ __webpack_require__.m = modules; 33 | 34 | /******/ // expose the module cache 35 | /******/ __webpack_require__.c = installedModules; 36 | 37 | /******/ // identity function for calling harmony imports with the correct context 38 | /******/ __webpack_require__.i = function(value) { return value; }; 39 | 40 | /******/ // define getter function for harmony exports 41 | /******/ __webpack_require__.d = function(exports, name, getter) { 42 | /******/ if(!__webpack_require__.o(exports, name)) { 43 | /******/ Object.defineProperty(exports, name, { 44 | /******/ configurable: false, 45 | /******/ enumerable: true, 46 | /******/ get: getter 47 | /******/ }); 48 | /******/ } 49 | /******/ }; 50 | 51 | /******/ // getDefaultExport function for compatibility with non-harmony modules 52 | /******/ __webpack_require__.n = function(module) { 53 | /******/ var getter = module && module.__esModule ? 54 | /******/ function getDefault() { return module['default']; } : 55 | /******/ function getModuleExports() { return module; }; 56 | /******/ __webpack_require__.d(getter, 'a', getter); 57 | /******/ return getter; 58 | /******/ }; 59 | 60 | /******/ // Object.prototype.hasOwnProperty.call 61 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 62 | 63 | /******/ // __webpack_public_path__ 64 | /******/ __webpack_require__.p = ""; 65 | 66 | /******/ // Load entry module and return exports 67 | /******/ return __webpack_require__(__webpack_require__.s = 5); 68 | /******/ }) 69 | /************************************************************************/ 70 | /******/ ([ 71 | /* 0 */ 72 | /***/ (function(module, exports) { 73 | 74 | module.exports = 75 | /******/ (function(modules) { // webpackBootstrap 76 | /******/ // The module cache 77 | /******/ var installedModules = {}; 78 | /******/ 79 | /******/ // The require function 80 | /******/ function __webpack_require__(moduleId) { 81 | /******/ 82 | /******/ // Check if module is in cache 83 | /******/ if(installedModules[moduleId]) { 84 | /******/ return installedModules[moduleId].exports; 85 | /******/ } 86 | /******/ // Create a new module (and put it into the cache) 87 | /******/ var module = installedModules[moduleId] = { 88 | /******/ i: moduleId, 89 | /******/ l: false, 90 | /******/ exports: {} 91 | /******/ }; 92 | /******/ 93 | /******/ // Execute the module function 94 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 95 | /******/ 96 | /******/ // Flag the module as loaded 97 | /******/ module.l = true; 98 | /******/ 99 | /******/ // Return the exports of the module 100 | /******/ return module.exports; 101 | /******/ } 102 | /******/ 103 | /******/ 104 | /******/ // expose the modules object (__webpack_modules__) 105 | /******/ __webpack_require__.m = modules; 106 | /******/ 107 | /******/ // expose the module cache 108 | /******/ __webpack_require__.c = installedModules; 109 | /******/ 110 | /******/ // identity function for calling harmony imports with the correct context 111 | /******/ __webpack_require__.i = function(value) { return value; }; 112 | /******/ 113 | /******/ // define getter function for harmony exports 114 | /******/ __webpack_require__.d = function(exports, name, getter) { 115 | /******/ if(!__webpack_require__.o(exports, name)) { 116 | /******/ Object.defineProperty(exports, name, { 117 | /******/ configurable: false, 118 | /******/ enumerable: true, 119 | /******/ get: getter 120 | /******/ }); 121 | /******/ } 122 | /******/ }; 123 | /******/ 124 | /******/ // getDefaultExport function for compatibility with non-harmony modules 125 | /******/ __webpack_require__.n = function(module) { 126 | /******/ var getter = module && module.__esModule ? 127 | /******/ function getDefault() { return module['default']; } : 128 | /******/ function getModuleExports() { return module; }; 129 | /******/ __webpack_require__.d(getter, 'a', getter); 130 | /******/ return getter; 131 | /******/ }; 132 | /******/ 133 | /******/ // Object.prototype.hasOwnProperty.call 134 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 135 | /******/ 136 | /******/ // __webpack_public_path__ 137 | /******/ __webpack_require__.p = ""; 138 | /******/ 139 | /******/ // Load entry module and return exports 140 | /******/ return __webpack_require__(__webpack_require__.s = 2); 141 | /******/ }) 142 | /************************************************************************/ 143 | /******/ ([ 144 | /* 0 */ 145 | /***/ (function(module, exports, __webpack_require__) { 146 | 147 | "use strict"; 148 | 149 | 150 | Object.defineProperty(exports, "__esModule", { 151 | value: true 152 | }); 153 | 154 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 155 | 156 | /** 157 | * Functions to build nodes 158 | */ 159 | 160 | var apply = exports.apply = function apply(op, args) { 161 | var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 162 | return _extends({ 163 | type: 'Apply', 164 | op: op, 165 | args: args 166 | }, options); 167 | }; 168 | 169 | // Operations 170 | 171 | var neg = exports.neg = function neg(arg) { 172 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 173 | return apply('neg', [arg], options); 174 | }; // options: wasMinus 175 | var add = exports.add = function add() { 176 | for (var _len = arguments.length, terms = Array(_len), _key = 0; _key < _len; _key++) { 177 | terms[_key] = arguments[_key]; 178 | } 179 | 180 | return apply('add', terms); 181 | }; 182 | var sub = exports.sub = function sub(minuend, subtrahend) { 183 | return apply('add', [minuend, neg(subtrahend, { wasMinus: true })]); 184 | }; 185 | var mul = exports.mul = function mul() { 186 | for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 187 | args[_key2] = arguments[_key2]; 188 | } 189 | 190 | return apply('mul', args); 191 | }; 192 | var implicitMul = exports.implicitMul = function implicitMul() { 193 | for (var _len3 = arguments.length, args = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { 194 | args[_key3] = arguments[_key3]; 195 | } 196 | 197 | return apply('mul', args, { implicit: true }); 198 | }; 199 | var div = exports.div = function div(numerator, denominator) { 200 | return apply('div', [numerator, denominator]); 201 | }; 202 | var pow = exports.pow = function pow(base, exponent) { 203 | return apply('pow', [base, exponent]); 204 | }; 205 | var abs = exports.abs = function abs(arg) { 206 | return apply('abs', [arg]); 207 | }; 208 | var fact = exports.fact = function fact(arg) { 209 | return apply('fact', [arg]); 210 | }; 211 | var nthRoot = exports.nthRoot = function nthRoot(radicand, index) { 212 | return apply('nthRoot', [radicand, index || number('2')]); 213 | }; 214 | 215 | // Relations 216 | 217 | var eq = exports.eq = function eq() { 218 | for (var _len4 = arguments.length, args = Array(_len4), _key4 = 0; _key4 < _len4; _key4++) { 219 | args[_key4] = arguments[_key4]; 220 | } 221 | 222 | return apply('eq', args); 223 | }; 224 | var ne = exports.ne = function ne() { 225 | for (var _len5 = arguments.length, args = Array(_len5), _key5 = 0; _key5 < _len5; _key5++) { 226 | args[_key5] = arguments[_key5]; 227 | } 228 | 229 | return apply('ne', args); 230 | }; 231 | var lt = exports.lt = function lt() { 232 | for (var _len6 = arguments.length, args = Array(_len6), _key6 = 0; _key6 < _len6; _key6++) { 233 | args[_key6] = arguments[_key6]; 234 | } 235 | 236 | return apply('lt', args); 237 | }; 238 | var le = exports.le = function le() { 239 | for (var _len7 = arguments.length, args = Array(_len7), _key7 = 0; _key7 < _len7; _key7++) { 240 | args[_key7] = arguments[_key7]; 241 | } 242 | 243 | return apply('le', args); 244 | }; 245 | var gt = exports.gt = function gt() { 246 | for (var _len8 = arguments.length, args = Array(_len8), _key8 = 0; _key8 < _len8; _key8++) { 247 | args[_key8] = arguments[_key8]; 248 | } 249 | 250 | return apply('gt', args); 251 | }; 252 | var ge = exports.ge = function ge() { 253 | for (var _len9 = arguments.length, args = Array(_len9), _key9 = 0; _key9 < _len9; _key9++) { 254 | args[_key9] = arguments[_key9]; 255 | } 256 | 257 | return apply('ge', args); 258 | }; 259 | 260 | var identifier = exports.identifier = function identifier(name) { 261 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 262 | return _extends({ 263 | type: 'Identifier', 264 | name: name 265 | }, options); 266 | }; 267 | 268 | var number = exports.number = function number(value) { 269 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 270 | return _extends({ 271 | type: 'Number', 272 | value: value 273 | }, options); 274 | }; 275 | 276 | var parens = exports.parens = function parens(body) { 277 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 278 | return _extends({ 279 | type: 'Parentheses', 280 | body: body 281 | }, options); 282 | }; 283 | 284 | // deprecated aliases 285 | var parensNode = exports.parensNode = parens; 286 | var numberNode = exports.numberNode = number; 287 | var identifierNode = exports.identifierNode = identifier; 288 | var applyNode = exports.applyNode = apply; 289 | 290 | /***/ }), 291 | /* 1 */ 292 | /***/ (function(module, exports, __webpack_require__) { 293 | 294 | "use strict"; 295 | 296 | 297 | Object.defineProperty(exports, "__esModule", { 298 | value: true 299 | }); 300 | /** 301 | * Functions to query properties of nodes 302 | */ 303 | 304 | var isIdentifier = exports.isIdentifier = function isIdentifier(node) { 305 | return node && node.type === 'Identifier'; 306 | }; 307 | var isApply = exports.isApply = function isApply(node) { 308 | return node && node.type === 'Apply'; 309 | }; 310 | 311 | var isOperation = exports.isOperation = function isOperation(node) { 312 | return isApply(node) && !isNumber(node); 313 | }; 314 | var isFunction = exports.isFunction = function isFunction(node) { 315 | return isApply(node) && isIdentifier(node.op); 316 | }; 317 | 318 | // TODO: curry it? 319 | var _isOp = function _isOp(op, node) { 320 | return isApply(node) && node.op === op; 321 | }; 322 | 323 | var isAdd = exports.isAdd = function isAdd(node) { 324 | return _isOp('add', node); 325 | }; 326 | var isMul = exports.isMul = function isMul(node) { 327 | return _isOp('mul', node); 328 | }; 329 | var isDiv = exports.isDiv = function isDiv(node) { 330 | return _isOp('div', node); 331 | }; 332 | var isPow = exports.isPow = function isPow(node) { 333 | return _isOp('pow', node); 334 | }; 335 | var isNeg = exports.isNeg = function isNeg(node) { 336 | return _isOp('neg', node); 337 | }; 338 | var isPos = exports.isPos = function isPos(node) { 339 | return _isOp('pos', node); 340 | }; 341 | var isAbs = exports.isAbs = function isAbs(node) { 342 | return _isOp('abs', node); 343 | }; 344 | var isFact = exports.isFact = function isFact(node) { 345 | return _isOp('fact', node); 346 | }; 347 | var isNthRoot = exports.isNthRoot = function isNthRoot(node) { 348 | return _isOp('nthRoot', node); 349 | }; 350 | 351 | var relationIdentifierMap = { 352 | 'eq': '=', 353 | 'lt': '<', 354 | 'le': '<=', 355 | 'gt': '>', 356 | 'ge': '>=', 357 | 'ne': '!=' 358 | }; 359 | 360 | var isRel = exports.isRel = function isRel(node) { 361 | return isApply(node) && node.op in relationIdentifierMap; 362 | }; 363 | 364 | var isNumber = exports.isNumber = function isNumber(node) { 365 | if (node.type === 'Number') { 366 | return true; 367 | } else if (isNeg(node)) { 368 | return isNumber(node.args[0]); 369 | } else { 370 | return false; 371 | } 372 | }; 373 | 374 | // check if it's a number before trying to get its value 375 | var getValue = exports.getValue = function getValue(node) { 376 | if (node.type === 'Number') { 377 | return parseFloat(node.value); 378 | } else if (isNeg(node)) { 379 | return -getValue(node.args[0]); 380 | } else if (isPos(node)) { 381 | return getValue(node.args[0]); 382 | } 383 | }; 384 | 385 | /***/ }), 386 | /* 2 */ 387 | /***/ (function(module, exports, __webpack_require__) { 388 | 389 | "use strict"; 390 | 391 | 392 | Object.defineProperty(exports, "__esModule", { 393 | value: true 394 | }); 395 | exports.query = exports.build = undefined; 396 | 397 | var _build = __webpack_require__(0); 398 | 399 | var build = _interopRequireWildcard(_build); 400 | 401 | var _query = __webpack_require__(1); 402 | 403 | var query = _interopRequireWildcard(_query); 404 | 405 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } 406 | 407 | exports.build = build; 408 | exports.query = query; 409 | 410 | /***/ }) 411 | /******/ ]); 412 | 413 | /***/ }), 414 | /* 1 */ 415 | /***/ (function(module, exports, __webpack_require__) { 416 | 417 | "use strict"; 418 | 419 | 420 | Object.defineProperty(exports, "__esModule", { 421 | value: true 422 | }); 423 | 424 | var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); 425 | 426 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; 427 | 428 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /** 429 | * Parses a math string to an AST. 430 | * 431 | * Notes: 432 | * - The output AST tries to conform to the math-ast spec, but some aspects may 433 | * be a little off. This will be fixed in future versions. 434 | * - The input syntax covers the parts of the mathjs syntax being used by 435 | * mathsteps 436 | * 437 | * TODO: 438 | * - Better adherence to and more comprehensive coverage of the math-ast spec. 439 | * - Specify what the syntax is, e.g. operator precedence, implicit multiplication, 440 | * etc. 441 | */ 442 | 443 | 444 | exports.default = parse; 445 | 446 | var _mathNodes = __webpack_require__(0); 447 | 448 | var _mathTraverse = __webpack_require__(4); 449 | 450 | function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } 451 | 452 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 453 | 454 | function isIdentifierToken(token) { 455 | return token && /^[a-zA-Z][a-zA-Z0-9]*$/.test(token.value); 456 | } 457 | 458 | function isNumberToken(token) { 459 | return token && /^(\d*\.\d+|\d+\.\d*|\d+)$/.test(token.value); 460 | } 461 | 462 | var tokenPattern = /\.\.\.|[a-zA-Z][a-zA-Z0-9]*|<=|>=|!=|[\<\>\!\=\(\)\+\-\/\*\^\<\>|\,\#\_]|\d*\.\d+|\d+\.\d*|\d+/; 463 | 464 | var relationTokenMap = { 465 | '=': 'eq', 466 | '<': 'lt', 467 | '<=': 'le', 468 | '>': 'gt', 469 | '>=': 'ge', 470 | '!=': 'ne' 471 | }; 472 | 473 | function isIdentifier(node) { 474 | return node.type === 'Identifier'; 475 | } 476 | 477 | function isRelation(node) { 478 | return node.type === 'Apply' && Object.values(relationTokenMap).includes(node.op); 479 | } 480 | 481 | function isExpression(node) { 482 | return ['System', 'List', 'Sequence'].indexOf(node.type) === -1 || isRelation(node); 483 | } 484 | 485 | function matches(token, value) { 486 | return token && token.value === value; 487 | } 488 | 489 | var Parser = function () { 490 | function Parser() { 491 | _classCallCheck(this, Parser); 492 | } 493 | 494 | _createClass(Parser, [{ 495 | key: 'consume', 496 | value: function consume(expectedValue) { 497 | var token = this.currentToken(); 498 | if (expectedValue !== undefined) { 499 | if (!matches(token, expectedValue)) { 500 | throw new Error('expected \'' + expectedValue + '\' received \'' + token.value + '\''); 501 | } 502 | } 503 | this.i++; 504 | return token; 505 | } 506 | }, { 507 | key: 'currentToken', 508 | value: function currentToken() { 509 | return this.tokens[this.i]; 510 | } 511 | }, { 512 | key: 'parse', 513 | value: function parse(input) { 514 | this.i = 0; 515 | this.tokens = []; 516 | this.integrals = 0; 517 | 518 | var regex = new RegExp(tokenPattern, 'g'); 519 | 520 | var index = 0; 521 | var match = void 0; 522 | 523 | while ((match = regex.exec(input)) != null) { 524 | var start = match.index; 525 | var end = regex.lastIndex; 526 | 527 | this.tokens.push({ 528 | value: match[0], 529 | start: start, 530 | end: end 531 | }); 532 | 533 | if (index !== start) { 534 | var skipped = input.slice(index, start).trim(); 535 | if (skipped !== '') { 536 | throw new Error('\'' + skipped + '\' not recognized'); 537 | } 538 | } 539 | 540 | index = end; 541 | } 542 | 543 | if (index !== input.length) { 544 | var _skipped = input.slice(index, input.length).trim(); 545 | if (_skipped !== '') { 546 | throw new Error('\'' + _skipped + '\' not recognized'); 547 | } 548 | } 549 | 550 | var result = this.list(); 551 | 552 | if (this.i < this.tokens.length) { 553 | throw new Error('extra input not recognized'); 554 | } 555 | 556 | return result; 557 | } 558 | }, { 559 | key: 'list', 560 | value: function list() { 561 | var items = [this.relationsOrRelationOrExpression()]; 562 | 563 | while (true) { 564 | var token = this.currentToken(); 565 | 566 | if (matches(token, ',')) { 567 | this.consume(','); 568 | items.push(this.relationsOrRelationOrExpression()); 569 | } else { 570 | break; 571 | } 572 | } 573 | 574 | if (items.length > 1) { 575 | if (items.every(function (item) { 576 | return isRelation(item); 577 | })) { 578 | return { 579 | type: 'System', // of equations 580 | relations: items 581 | }; 582 | } else if (items.every(isExpression)) { 583 | return { 584 | type: 'Sequence', 585 | items: items 586 | }; 587 | } else { 588 | return { 589 | type: 'List', 590 | items: items 591 | }; 592 | } 593 | } else { 594 | return items[0]; 595 | } 596 | } 597 | }, { 598 | key: 'relationsOrRelationOrExpression', 599 | value: function relationsOrRelationOrExpression() { 600 | var relations = []; 601 | var args = []; 602 | 603 | var left = void 0; 604 | var right = void 0; 605 | 606 | left = this.expression(); 607 | 608 | while (true) { 609 | var token = this.currentToken(); 610 | 611 | if (token && token.value in relationTokenMap) { 612 | this.consume(); 613 | right = this.expression(); 614 | var rel = relationTokenMap[token.value]; 615 | relations.push(_mathNodes.build.applyNode(rel, [left, right])); 616 | args.push(left); 617 | left = right; 618 | } else { 619 | break; 620 | } 621 | } 622 | args.push(right); 623 | 624 | if (relations.length > 1) { 625 | var _ret = function () { 626 | relations.reverse(); 627 | 628 | var output = { 629 | type: 'Apply', 630 | op: 'and', 631 | args: [] 632 | }; 633 | 634 | var current = { 635 | type: 'Apply', 636 | op: relations[0].op, 637 | args: [] 638 | }; 639 | 640 | relations.forEach(function (item) { 641 | if (item.op !== current.op) { 642 | current.args.unshift(item.args[1]); 643 | 644 | output.args.unshift(current); 645 | 646 | current = { 647 | type: 'Apply', 648 | op: item.op, 649 | args: [item.args[1]] 650 | }; 651 | } else { 652 | current.args.unshift(item.args[1]); 653 | } 654 | }); 655 | 656 | current.args.unshift(relations[relations.length - 1].args[0]); 657 | output.args.unshift(current); 658 | 659 | if (output.args.length === 1) { 660 | return { 661 | v: output.args[0] 662 | }; 663 | } 664 | 665 | return { 666 | v: output 667 | }; 668 | }(); 669 | 670 | if ((typeof _ret === 'undefined' ? 'undefined' : _typeof(_ret)) === "object") return _ret.v; 671 | } else if (relations.length > 0) { 672 | return relations[0]; 673 | } else { 674 | return left; 675 | } 676 | } 677 | }, { 678 | key: 'expression', 679 | value: function expression() { 680 | var args = []; 681 | 682 | args.push(this.explicitMul()); 683 | 684 | while (true) { 685 | var token = this.currentToken(); 686 | 687 | if (matches(token, '-')) { 688 | this.consume('-'); 689 | args.push(_mathNodes.build.applyNode('neg', [this.explicitMul()], { wasMinus: true })); 690 | } else if (matches(token, '+')) { 691 | this.consume('+'); 692 | args.push(this.explicitMul()); 693 | } else { 694 | break; 695 | } 696 | } 697 | 698 | if (args.length > 1) { 699 | return _mathNodes.build.applyNode('add', args.map(function (term) { 700 | return term.addParens ? _mathNodes.build.parensNode(term) : term; 701 | })); 702 | } else { 703 | if (args[0].addParens) { 704 | return _mathNodes.build.parensNode(args[0]); 705 | } else { 706 | return args[0]; 707 | } 708 | } 709 | } 710 | }, { 711 | key: 'explicitMul', 712 | value: function explicitMul() { 713 | var factors = []; 714 | 715 | factors.push(this.implicitMul()); 716 | 717 | while (true) { 718 | if (matches(this.currentToken(), '*')) { 719 | this.consume('*'); 720 | factors.push(this.implicitMul()); 721 | } else { 722 | break; 723 | } 724 | } 725 | 726 | return factors.length > 1 ? _mathNodes.build.applyNode('mul', factors) : factors[0]; 727 | } 728 | 729 | /** 730 | * Parse the following forms of implicit multiplication: 731 | * - a b c 732 | * - (a)(b)(c) 733 | * 734 | * Note: (a)b(c) is actually: 'a' times function 'b' evaluated at 'c' 735 | * 736 | * If the multiplication was detected, a single parsed factor is returned. 737 | */ 738 | 739 | }, { 740 | key: 'implicitMul', 741 | value: function implicitMul() { 742 | var factors = []; 743 | 744 | factors.push(this.division()); 745 | 746 | while (true) { 747 | var token = this.currentToken(); 748 | 749 | if (matches(token, '(') || matches(token, '#') || isIdentifierToken(token) || isNumberToken(token)) { 750 | var factor = this.division(); 751 | if (factor) { 752 | factors.push(factor); 753 | } else { 754 | break; 755 | } 756 | } else { 757 | break; 758 | } 759 | 760 | if (this.i > this.tokens.length) { 761 | break; 762 | } 763 | } 764 | 765 | return factors.length > 1 ? _mathNodes.build.applyNode('mul', factors, { implicit: true }) : factors[0]; 766 | } 767 | }, { 768 | key: 'division', 769 | value: function division() { 770 | var numerator = void 0; 771 | var denominator = void 0; 772 | var frac = void 0; 773 | 774 | numerator = this.factor(); 775 | 776 | while (true) { 777 | var token = this.currentToken(); 778 | 779 | if (matches(token, '/')) { 780 | this.consume('/'); 781 | denominator = this.factor(); 782 | if (frac) { 783 | frac = _mathNodes.build.applyNode('div', [frac, denominator]); 784 | } else { 785 | frac = _mathNodes.build.applyNode('div', [numerator, denominator]); 786 | } 787 | } else { 788 | break; 789 | } 790 | } 791 | 792 | return frac || numerator; 793 | } 794 | 795 | /** 796 | * Parse any of the following: 797 | * - unary operations, e.g. +, - 798 | * - numbers 799 | * - identifiers 800 | * - parenthesis 801 | * - absolute value function, e.g. |x| 802 | * - exponents, e.g. x^2 803 | */ 804 | 805 | }, { 806 | key: 'factor', 807 | value: function factor() { 808 | var token = this.currentToken(); 809 | 810 | if (matches(token, '...')) { 811 | this.consume('...'); 812 | return { 813 | type: 'Ellipsis' 814 | }; 815 | } 816 | 817 | var signs = []; 818 | 819 | // handle multiple unary operators 820 | while (matches(token, '+') || matches(token, '-')) { 821 | signs.push(token); 822 | this.consume(token.value); 823 | token = this.currentToken(); 824 | } 825 | 826 | var base = void 0, 827 | exp = void 0; 828 | var addParens = false; 829 | 830 | if (matches(token, '#') || isIdentifierToken(token)) { 831 | var node = this.identifierOrPlaceholder(); 832 | 833 | if (this.integrals > 0 && isIdentifier(node) && /d[a-z]+/.test(node.name)) { 834 | // backup 835 | this.integrals--; 836 | return; 837 | } 838 | 839 | if (matches(this.currentToken(), '(')) { 840 | this.consume('('); 841 | var args = this.argumentList(); 842 | token = this.consume(')'); 843 | if (node.name === 'nthRoot') { 844 | if (args.length < 1 || args.length > 2) { 845 | throw new Error('nthRoot takes 1 or 2 args'); 846 | } else { 847 | base = _mathNodes.build.nthRoot.apply(_mathNodes.build, _toConsumableArray(args)); 848 | } 849 | } else if (node.name === 'int') { 850 | if (args.length >= 2 && args.length <= 4) { 851 | base = _mathNodes.build.apply('int', args); 852 | } else { 853 | throw new Error('integral takes between 2 and 4 args'); 854 | } 855 | } else { 856 | if (_mathNodes.query.isIdentifier(node) && node.name === 'abs') { 857 | base = _mathNodes.build.apply('abs', args); 858 | } else { 859 | base = _mathNodes.build.apply(node, args); 860 | } 861 | } 862 | } else { 863 | // TODO(kevinb) valid the constraint type against the node 864 | // e.g. if it's a 'Number' then it can't have a subscript 865 | base = node; 866 | } 867 | } else if (isNumberToken(token)) { 868 | this.consume(token.value); 869 | base = _mathNodes.build.numberNode(token.value); 870 | } else if (matches(token, '(')) { 871 | this.consume('('); 872 | base = this.expression(); 873 | token = this.consume(')'); 874 | addParens = true; 875 | if (base.type === 'Number' || isIdentifier(base)) { 876 | base = _mathNodes.build.parensNode(base); 877 | addParens = false; 878 | } 879 | } else if (matches(token, '|')) { 880 | this.consume('|'); 881 | base = this.expression(); 882 | token = this.consume('|'); 883 | 884 | base = _mathNodes.build.applyNode('abs', [base]); 885 | } 886 | 887 | if (matches(this.currentToken(), '!')) { 888 | this.consume('!'); 889 | // print will add parentheses back in if a 'fact' node wraps the 890 | // expression. 891 | addParens = false; 892 | base = _mathNodes.build.applyNode('fact', [base]); 893 | } 894 | 895 | var factor = base; 896 | 897 | // TODO handle exponents separately 898 | if (matches(this.currentToken(), '^')) { 899 | this.consume('^'); 900 | exp = this.factor(); 901 | factor = _mathNodes.build.applyNode('pow', [base, exp]); 902 | addParens = false; 903 | } 904 | 905 | // Reverse the signs so that we process them from the sign nearest 906 | // to the factor to the furthest. 907 | signs.reverse(); 908 | 909 | signs.forEach(function (sign) { 910 | if (sign.value === '+') { 911 | factor = _mathNodes.build.applyNode('pos', [factor]); 912 | } else { 913 | factor = _mathNodes.build.applyNode('neg', [factor]); 914 | } 915 | addParens = false; 916 | }); 917 | 918 | if (addParens) { 919 | factor.addParens = addParens; 920 | } 921 | 922 | if (_mathNodes.query.isPow(factor)) { 923 | var _factor$args = _slicedToArray(factor.args, 2), 924 | _base = _factor$args[0], 925 | exponent = _factor$args[1]; 926 | 927 | if (_mathNodes.query.isIdentifier(_base) && _base.name === 'int') { 928 | this.integrals++; 929 | var body = this.expression(); 930 | 931 | // backup to get the token we ignore 932 | this.i--; 933 | token = this.currentToken(); 934 | this.consume(token.value); 935 | 936 | var result = { 937 | type: 'Apply', 938 | op: 'int', 939 | args: [body, _mathNodes.build.identifier(token.value)], 940 | limits: [_base.subscript, exponent] 941 | }; 942 | 943 | return result; 944 | } 945 | } else if (_mathNodes.query.isIdentifier(factor) && factor.name === 'int') { 946 | this.integrals++; 947 | var _body = this.expression(); 948 | 949 | // backup to get the token we ignore 950 | this.i--; 951 | token = this.currentToken(); 952 | this.consume(token.value); 953 | 954 | var _result = { 955 | type: 'Apply', 956 | op: 'int', 957 | args: [_body, _mathNodes.build.identifier(token.value)] 958 | }; 959 | 960 | return _result; 961 | } 962 | 963 | return factor; 964 | } 965 | }, { 966 | key: 'identifierOrPlaceholder', 967 | value: function identifierOrPlaceholder() { 968 | var token = this.currentToken(); 969 | 970 | var isPlaceholder = false; 971 | if (matches(token, '#')) { 972 | isPlaceholder = true; 973 | this.consume('#'); 974 | token = this.currentToken(); 975 | } 976 | 977 | if (!isIdentifierToken(token)) { 978 | throw new Error('\'#\' must be followed by an identifier'); 979 | } 980 | 981 | var result = this.identifier(); 982 | 983 | if (isPlaceholder) { 984 | result.type = 'Placeholder'; 985 | } 986 | 987 | return result; 988 | } 989 | }, { 990 | key: 'identifier', 991 | value: function identifier() { 992 | var token = this.currentToken(); 993 | 994 | var name = token.value; 995 | var result = _mathNodes.build.identifierNode(name); 996 | this.consume(name); 997 | 998 | token = this.currentToken(); 999 | 1000 | // This only handles very simple subscripts, e.g. a_0, a_n 1001 | // It doesn't handle: a_-1, a_(m+n), etc. 1002 | // The precedence of subscripts is very high: a_0^2 => (a_0)^2 1003 | if (matches(token, '_')) { 1004 | this.consume('_'); 1005 | 1006 | token = this.currentToken(); 1007 | 1008 | if (isNumberToken(token)) { 1009 | result.subscript = _mathNodes.build.numberNode(token.value); 1010 | this.consume(token.value); 1011 | } else if (isIdentifierToken(token)) { 1012 | result.subscript = _mathNodes.build.identifierNode(token.value); 1013 | this.consume(token.value); 1014 | } else { 1015 | throw new Error('Can\'t handle \'' + token.value + '\' as a subscript'); 1016 | } 1017 | } 1018 | 1019 | return result; 1020 | } 1021 | }, { 1022 | key: 'argumentList', 1023 | value: function argumentList() { 1024 | var args = [this.expression()]; 1025 | while (true) { 1026 | var token = this.currentToken(); 1027 | if (!matches(token, ',')) { 1028 | break; 1029 | } 1030 | this.consume(','); 1031 | args.push(this.expression()); 1032 | } 1033 | return args; 1034 | } 1035 | }]); 1036 | 1037 | return Parser; 1038 | }(); 1039 | 1040 | var parser = new Parser(); 1041 | 1042 | function parse(math) { 1043 | var ast = parser.parse(math); 1044 | (0, _mathTraverse.traverse)(ast, { 1045 | leave: function leave(node) { 1046 | if (node.hasOwnProperty('addParens')) { 1047 | delete node.addParens; 1048 | } 1049 | } 1050 | }); 1051 | return ast; 1052 | } 1053 | 1054 | /***/ }), 1055 | /* 2 */ 1056 | /***/ (function(module, exports, __webpack_require__) { 1057 | 1058 | "use strict"; 1059 | 1060 | 1061 | Object.defineProperty(exports, "__esModule", { 1062 | value: true 1063 | }); 1064 | 1065 | var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); /** 1066 | * print - return a string representation of the nodes. 1067 | */ 1068 | 1069 | 1070 | exports.default = print; 1071 | 1072 | var _mathNodes = __webpack_require__(0); 1073 | 1074 | var relationIdentifierMap = { 1075 | 'eq': '=', 1076 | 'lt': '<', 1077 | 'le': '<=', 1078 | 'gt': '>', 1079 | 'ge': '>=', 1080 | 'ne': '!=' 1081 | }; 1082 | 1083 | var printApply = function printApply(node, parent) { 1084 | var op = node.op, 1085 | args = node.args; 1086 | 1087 | 1088 | if (op === 'add') { 1089 | var result = print(args[0], node); 1090 | for (var i = 1; i < args.length; i++) { 1091 | var arg = args[i]; 1092 | if (_mathNodes.query.isNeg(arg) && arg.wasMinus) { 1093 | result += ' - ' + print(arg.args[0], node); 1094 | } else { 1095 | result += ' + ' + print(arg, node); 1096 | } 1097 | } 1098 | return parent && !_mathNodes.query.isRel(parent) && parent.type !== 'Parentheses' ? '(' + result + ')' : result; 1099 | } else if (op === 'mul') { 1100 | var _result = void 0; 1101 | // print coefficients with no spaces when possible - e.g. 2x not 2 x 1102 | // if there are more than 3 args, we can't do 2xy because if that was 1103 | // reparsed it'd treat xy as one symbol 1104 | if (node.implicit && node.args.length === 2 && _mathNodes.query.isNumber(node.args[0]) && _mathNodes.query.isIdentifier(node.args[1])) { 1105 | _result = args.map(function (arg) { 1106 | return print(arg, node); 1107 | }).join(''); 1108 | } else if (node.implicit) { 1109 | _result = args.map(function (arg) { 1110 | return print(arg, node); 1111 | }).join(' '); 1112 | } else { 1113 | _result = args.map(function (arg) { 1114 | return print(arg, node); 1115 | }).join(' * '); 1116 | } 1117 | 1118 | if (_mathNodes.query.isMul(parent)) { 1119 | if (node.implicit && !parent.implicit) { 1120 | return _result; 1121 | } else { 1122 | return '(' + _result + ')'; 1123 | } 1124 | } else if (_mathNodes.query.isPow(parent) || _mathNodes.query.isDiv(parent)) { 1125 | return '(' + _result + ')'; 1126 | } else { 1127 | return _result; 1128 | } 1129 | } else if (op === 'div') { 1130 | var _result2 = ''; 1131 | // this lets us print things like 2/3 and x/5 instead of 2 / 3 and x / 5 1132 | // (but the spaces are helpful for reading more complicated fractions) 1133 | if ((_mathNodes.query.isIdentifier(args[0]) || _mathNodes.query.isNumber(args[0])) && (_mathNodes.query.isIdentifier(args[1]) || _mathNodes.query.isNumber(args[1]))) { 1134 | _result2 = print(args[0]) + '/' + print(args[1]); 1135 | } else { 1136 | _result2 += print(args[0], node); 1137 | _result2 += ' / '; 1138 | if (_mathNodes.query.isDiv(args[1])) { 1139 | _result2 += '(' + print(args[1], node) + ')'; 1140 | } else { 1141 | _result2 += print(args[1], node); 1142 | } 1143 | } 1144 | return _mathNodes.query.isPow(parent) ? '(' + _result2 + ')' : _result2; 1145 | } else if (op === 'pow') { 1146 | var _node$args = _slicedToArray(node.args, 2), 1147 | base = _node$args[0], 1148 | exp = _node$args[1]; 1149 | 1150 | return _mathNodes.query.isNeg(base) ? '(' + print(base, node) + ')^' + print(exp, node) : print(base, node) + '^' + print(exp, node); 1151 | } else if (op === 'neg') { 1152 | return '-' + print(args[0], node); 1153 | } else if (op === 'pos') { 1154 | return '+' + print(args[0], node); 1155 | } else if (op === 'pn') { 1156 | throw new Error('we don\'t handle \'pn\' operations yet'); 1157 | } else if (op === 'np') { 1158 | throw new Error('we don\'t handle \'np\' operations yet'); 1159 | } else if (op === 'fact') { 1160 | if (args[0].op === 'pow' || args[0].op === 'mul' || args[0].op === 'div') { 1161 | return '(' + print(args[0], node) + ')!'; 1162 | } else { 1163 | return print(args[0], node) + '!'; 1164 | } 1165 | } else if (op === 'nthRoot') { 1166 | return 'nthRoot(' + args.map(function (arg) { 1167 | return print(arg, node); 1168 | }).join(', ') + ')'; 1169 | } else if (op === 'int') { 1170 | return 'int(' + args.map(function (arg) { 1171 | return print(arg, node); 1172 | }).join(', ') + ')'; 1173 | } else if (op === 'abs') { 1174 | return '|' + print(args[0]) + '|'; 1175 | } else if (op in relationIdentifierMap) { 1176 | var symbol = relationIdentifierMap[op]; 1177 | return args.map(function (arg) { 1178 | return print(arg, node); 1179 | }).join(' ' + symbol + ' '); 1180 | } else { 1181 | return print(op) + '(' + args.map(function (arg) { 1182 | return print(arg, node); 1183 | }).join(', ') + ')'; 1184 | } 1185 | }; 1186 | 1187 | function print(node) { 1188 | var parent = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; 1189 | 1190 | switch (node.type) { 1191 | // regular non-leaf nodes 1192 | case 'Apply': 1193 | return printApply(node, parent); 1194 | 1195 | // irregular non-leaf nodes 1196 | case 'Parentheses': 1197 | return '(' + print(node.body, node) + ')'; 1198 | 1199 | case 'Sequence': 1200 | return node.items.map(print).join(', '); 1201 | 1202 | // leaf nodes 1203 | case 'Identifier': 1204 | if (node.subscript) { 1205 | return node.name + '_' + print(node.subscript); 1206 | } else { 1207 | return node.name; 1208 | } 1209 | 1210 | case 'Placeholder': 1211 | if (node.subscript) { 1212 | return '#' + node.name + '_' + print(node.subscript); 1213 | } else { 1214 | return '#' + node.name; 1215 | } 1216 | 1217 | case 'Number': 1218 | return node.value; 1219 | 1220 | case 'Ellipsis': 1221 | return '...'; 1222 | 1223 | default: 1224 | console.log(node); // eslint-disable-line no-console 1225 | throw new Error('unrecognized node'); 1226 | } 1227 | } 1228 | 1229 | /***/ }), 1230 | /* 3 */ 1231 | /***/ (function(module, exports, __webpack_require__) { 1232 | 1233 | "use strict"; 1234 | 1235 | 1236 | Object.defineProperty(exports, "__esModule", { 1237 | value: true 1238 | }); 1239 | 1240 | var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); /** 1241 | * toTex - return a string representation of the nodes in LaTeX 1242 | */ 1243 | 1244 | exports.default = toTex; 1245 | 1246 | var _mathNodes = __webpack_require__(0); 1247 | 1248 | var relationIdentifierMap = { 1249 | 'eq': '=', 1250 | 'lt': '<', 1251 | 'le': '<=', 1252 | 'gt': '>', 1253 | 'ge': '>=', 1254 | 'ne': '!=' 1255 | }; 1256 | 1257 | var applyToTex = function applyToTex(node, parent) { 1258 | var op = node.op, 1259 | args = node.args; 1260 | 1261 | 1262 | if (op === 'add') { 1263 | //e.g a + (-a) => a - a 1264 | var result = toTex(node.args[0], node); 1265 | for (var i = 1; i < node.args.length; i++) { 1266 | var arg = node.args[i]; 1267 | if (_mathNodes.query.isNeg(arg) && arg.wasMinus) { 1268 | result += ' - ' + toTex(arg.args[0], node); 1269 | } else { 1270 | result += ' + ' + toTex(arg, node); 1271 | } 1272 | } 1273 | return parent ? '\\left(' + result + '\\right)' : result; 1274 | } else if (op === 'mul') { 1275 | if (node.implicit) { 1276 | //e.g 2 x 1277 | return node.args.map(function (arg) { 1278 | return toTex(arg, node); 1279 | }).join(' '); 1280 | } else { 1281 | //e.g 2 * x 1282 | return node.args.map(function (arg) { 1283 | return toTex(arg, node); 1284 | }).join(' \\times '); 1285 | } 1286 | } else if (op === 'div') { 1287 | var _result = ''; 1288 | _result += '\\frac'; 1289 | //add parentheses when numerator or denominator has multiple terms 1290 | //e.g latex fractions: \frac{a}{b} => a/b 1291 | _result += '{' + toTex(node.args[0], node) + '}'; 1292 | _result += '{' + toTex(node.args[1], node) + '}'; 1293 | return _result; 1294 | } else if (op === 'pow') { 1295 | return toTex(node.args[0], node) + '^{' + toTex(node.args[1], node) + '}'; 1296 | } else if (op === 'neg') { 1297 | return '-' + toTex(args[0], node); 1298 | } else if (op === 'pos') { 1299 | return '+' + toTex(args[0], node); 1300 | } else if (op === 'pn') { 1301 | throw new Error('we don\'t handle \'pn\' operations yet'); 1302 | } else if (op === 'np') { 1303 | throw new Error('we don\'t handle \'np\' operations yet'); 1304 | } else if (op === 'fact') { 1305 | throw new Error('we dont handle fact operations yet'); 1306 | } else if (op === 'nthRoot') { 1307 | var _node$args = _slicedToArray(node.args, 2), 1308 | radicand = _node$args[0], 1309 | index = _node$args[1]; 1310 | 1311 | var base = void 0, 1312 | exponent = void 0; 1313 | var _result2 = void 0; 1314 | 1315 | if (_mathNodes.query.isPow(radicand)) { 1316 | var _radicand$args = _slicedToArray(radicand.args, 2); 1317 | 1318 | base = _radicand$args[0]; 1319 | exponent = _radicand$args[1]; 1320 | 1321 | _result2 = index == '' || _mathNodes.query.getValue(index) == 2 ? '\\sqrt{' + toTex(base, node) + '^' + toTex(exponent, node) + '}' : '\\sqrt[' + toTex(index, node) + ']{' + toTex(base, node) + '^' + toTex(exponent, node) + '}'; 1322 | } else { 1323 | base = radicand; 1324 | _result2 = index == '' || _mathNodes.query.getValue(index) == 2 ? '\\sqrt{' + toTex(base, node) + '}' : '\\sqrt[' + toTex(index, node) + ']{' + toTex(base, node) + '}'; 1325 | } 1326 | return _result2; 1327 | } else if (op === 'int') { 1328 | var _result3 = void 0; 1329 | if (node.args.length == 2) { 1330 | var _node$args2 = _slicedToArray(node.args, 2), 1331 | input = _node$args2[0], 1332 | variable = _node$args2[1]; 1333 | 1334 | _result3 = '\\int ' + toTex(input, node) + ' d' + toTex(variable, node); 1335 | } else if (node.args.length == 3) { 1336 | var _node$args3 = _slicedToArray(node.args, 3), 1337 | _input = _node$args3[0], 1338 | _variable = _node$args3[1], 1339 | domain = _node$args3[2]; 1340 | 1341 | _result3 = '\\int_' + toTex(domain, node) + ' ' + toTex(_input, node) + ' d' + toTex(_variable, node); 1342 | } else if (node.args.length == 4) { 1343 | var _node$args4 = _slicedToArray(node.args, 4), 1344 | _input2 = _node$args4[0], 1345 | _variable2 = _node$args4[1], 1346 | upper = _node$args4[2], 1347 | lower = _node$args4[3]; 1348 | 1349 | _result3 = '\\int_{' + toTex(upper, node) + '}^{' + toTex(lower, node) + '} ' + toTex(_input2, node) + ' d' + toTex(_variable2, node); 1350 | } 1351 | return _result3; 1352 | } else if (op in relationIdentifierMap) { 1353 | var symbol = relationIdentifierMap[op]; 1354 | return args.map(function (arg) { 1355 | return toTex(arg, node); 1356 | }).join(' ' + symbol + ' '); 1357 | } else { 1358 | return op + '(' + args.map(function (arg) { 1359 | return toTex(arg, node); 1360 | }).join(', ') + ')'; 1361 | } 1362 | }; 1363 | 1364 | function toTex(node) { 1365 | var parent = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; 1366 | 1367 | switch (node.type) { 1368 | // regular non-leaf nodes 1369 | case 'Apply': 1370 | return applyToTex(node, parent); 1371 | 1372 | // irregular node-leaf nodes 1373 | case 'Parentheses': 1374 | return 'left(' + toTex(node.body, node) + '\right)'; 1375 | 1376 | //leaf nodes 1377 | case 'Identifier': 1378 | return node.name; 1379 | case 'Number': 1380 | return node.value; 1381 | 1382 | default: 1383 | console.log(node); // eslint-disable-line no-console 1384 | throw new Error('unrecognized node'); 1385 | } 1386 | } 1387 | 1388 | /***/ }), 1389 | /* 4 */ 1390 | /***/ (function(module, exports) { 1391 | 1392 | module.exports = 1393 | /******/ (function(modules) { // webpackBootstrap 1394 | /******/ // The module cache 1395 | /******/ var installedModules = {}; 1396 | /******/ 1397 | /******/ // The require function 1398 | /******/ function __webpack_require__(moduleId) { 1399 | /******/ 1400 | /******/ // Check if module is in cache 1401 | /******/ if(installedModules[moduleId]) { 1402 | /******/ return installedModules[moduleId].exports; 1403 | /******/ } 1404 | /******/ // Create a new module (and put it into the cache) 1405 | /******/ var module = installedModules[moduleId] = { 1406 | /******/ i: moduleId, 1407 | /******/ l: false, 1408 | /******/ exports: {} 1409 | /******/ }; 1410 | /******/ 1411 | /******/ // Execute the module function 1412 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 1413 | /******/ 1414 | /******/ // Flag the module as loaded 1415 | /******/ module.l = true; 1416 | /******/ 1417 | /******/ // Return the exports of the module 1418 | /******/ return module.exports; 1419 | /******/ } 1420 | /******/ 1421 | /******/ 1422 | /******/ // expose the modules object (__webpack_modules__) 1423 | /******/ __webpack_require__.m = modules; 1424 | /******/ 1425 | /******/ // expose the module cache 1426 | /******/ __webpack_require__.c = installedModules; 1427 | /******/ 1428 | /******/ // identity function for calling harmony imports with the correct context 1429 | /******/ __webpack_require__.i = function(value) { return value; }; 1430 | /******/ 1431 | /******/ // define getter function for harmony exports 1432 | /******/ __webpack_require__.d = function(exports, name, getter) { 1433 | /******/ if(!__webpack_require__.o(exports, name)) { 1434 | /******/ Object.defineProperty(exports, name, { 1435 | /******/ configurable: false, 1436 | /******/ enumerable: true, 1437 | /******/ get: getter 1438 | /******/ }); 1439 | /******/ } 1440 | /******/ }; 1441 | /******/ 1442 | /******/ // getDefaultExport function for compatibility with non-harmony modules 1443 | /******/ __webpack_require__.n = function(module) { 1444 | /******/ var getter = module && module.__esModule ? 1445 | /******/ function getDefault() { return module['default']; } : 1446 | /******/ function getModuleExports() { return module; }; 1447 | /******/ __webpack_require__.d(getter, 'a', getter); 1448 | /******/ return getter; 1449 | /******/ }; 1450 | /******/ 1451 | /******/ // Object.prototype.hasOwnProperty.call 1452 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 1453 | /******/ 1454 | /******/ // __webpack_public_path__ 1455 | /******/ __webpack_require__.p = ""; 1456 | /******/ 1457 | /******/ // Load entry module and return exports 1458 | /******/ return __webpack_require__(__webpack_require__.s = 2); 1459 | /******/ }) 1460 | /************************************************************************/ 1461 | /******/ ([ 1462 | /* 0 */ 1463 | /***/ (function(module, exports, __webpack_require__) { 1464 | 1465 | "use strict"; 1466 | 1467 | 1468 | /** 1469 | * replace - visit all nodes in the tree with the ability to replace them. 1470 | * 1471 | * This function may modify the node passed in and/or any of its descendants. 1472 | * 1473 | * If neither 'enter' nor 'leave' return a value, the node is unchanged. 1474 | * If 'enter' returns a new node, the children of the new node will be traversed 1475 | * instead of the old one. If both 'enter' and 'leave' return values, the 1476 | * value returned by 'leave' is the node that will end up in the new AST. 1477 | */ 1478 | 1479 | function replace(node, _ref) { 1480 | var enter = _ref.enter, 1481 | leave = _ref.leave; 1482 | 1483 | var rep = enter && enter(node) || node; 1484 | 1485 | switch (rep.type) { 1486 | // regular non-leaf nodes 1487 | case 'Apply': 1488 | for (var i = 0; i < rep.args.length; i++) { 1489 | var arg = rep.args[i]; 1490 | rep.args[i] = replace(arg, { enter: enter, leave: leave }); 1491 | } 1492 | break; 1493 | 1494 | // Skip leaf nodes because they're handled by the enter/leave calls at 1495 | // the start/end of replace. 1496 | case 'Identifier': 1497 | case 'Number': 1498 | case 'Ellipsis': 1499 | break; 1500 | 1501 | // irregular non-leaf nodes 1502 | case 'Parentheses': 1503 | rep.body = replace(rep.body, { enter: enter, leave: leave }); 1504 | break; 1505 | 1506 | case 'List': 1507 | case 'Sequence': 1508 | for (var _i = 0; _i < rep.args.length; _i++) { 1509 | var item = rep.items[_i]; 1510 | rep.items[_i] = replace(item, { enter: enter, leave: leave }); 1511 | } 1512 | break; 1513 | 1514 | case 'System': 1515 | for (var _i2 = 0; _i2 < rep.relations.length; _i2++) { 1516 | var rel = rep.relations[_i2]; 1517 | rep.relations[_i2] = replace(rel, { enter: enter, leave: leave }); 1518 | } 1519 | break; 1520 | 1521 | case 'Placeholder': 1522 | // TODO(kevinb) handle children of the placeholder 1523 | // e.g. we there might #a_0 could match x_0, y_0, z_0, etc. 1524 | break; 1525 | 1526 | default: 1527 | throw new Error('unrecognized node'); 1528 | } 1529 | 1530 | return leave && leave(rep) || rep; 1531 | } 1532 | 1533 | module.exports = replace; 1534 | 1535 | /***/ }), 1536 | /* 1 */ 1537 | /***/ (function(module, exports, __webpack_require__) { 1538 | 1539 | "use strict"; 1540 | 1541 | 1542 | function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } 1543 | 1544 | /** 1545 | * traverse - walk all of the nodes in a tree. 1546 | */ 1547 | 1548 | function traverse(node, _ref) { 1549 | var _ref$enter = _ref.enter, 1550 | enter = _ref$enter === undefined ? function () {} : _ref$enter, 1551 | _ref$leave = _ref.leave, 1552 | leave = _ref$leave === undefined ? function () {} : _ref$leave; 1553 | var path = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : []; 1554 | 1555 | switch (node.type) { 1556 | // regular non-leaf nodes 1557 | case 'Apply': 1558 | enter(node, path); 1559 | node.args.forEach(function (arg, index) { 1560 | return traverse(arg, { enter: enter, leave: leave }, [].concat(_toConsumableArray(path), ['args', index])); 1561 | }); 1562 | leave(node, path); 1563 | break; 1564 | 1565 | // leaf nodes 1566 | case 'Identifier': 1567 | case 'Number': 1568 | case 'Ellipsis': 1569 | enter(node, path); 1570 | leave(node, path); 1571 | break; 1572 | 1573 | // irregular non-leaf nodes 1574 | case 'Parentheses': 1575 | enter(node, path); 1576 | traverse(node.body, { enter: enter, leave: leave }, [].concat(_toConsumableArray(path), ['body'])); 1577 | leave(node, path); 1578 | break; 1579 | 1580 | case 'List': 1581 | case 'Sequence': 1582 | enter(node, path); 1583 | node.items.forEach(function (item, index) { 1584 | return traverse(item, { enter: enter, leave: leave }, [].concat(_toConsumableArray(path), ['items', index])); 1585 | }); 1586 | leave(node, path); 1587 | break; 1588 | 1589 | case 'System': 1590 | enter(node, path); 1591 | node.relations.forEach(function (rel, index) { 1592 | return traverse(rel, { enter: enter, leave: leave }, [].concat(_toConsumableArray(path), ['relations', index])); 1593 | }); 1594 | leave(node, path); 1595 | break; 1596 | 1597 | case 'Placeholder': 1598 | // TODO(kevinb) handle children of the placeholder 1599 | // e.g. we there might #a_0 could match x_0, y_0, z_0, etc. 1600 | enter(node, path); 1601 | leave(node, path); 1602 | break; 1603 | 1604 | default: 1605 | throw new Error('unrecognized node: ' + node.type); 1606 | } 1607 | } 1608 | 1609 | module.exports = traverse; 1610 | 1611 | /***/ }), 1612 | /* 2 */ 1613 | /***/ (function(module, exports, __webpack_require__) { 1614 | 1615 | "use strict"; 1616 | 1617 | 1618 | module.exports = { 1619 | replace: __webpack_require__(0), 1620 | traverse: __webpack_require__(1) 1621 | }; 1622 | 1623 | /***/ }) 1624 | /******/ ]); 1625 | 1626 | /***/ }), 1627 | /* 5 */ 1628 | /***/ (function(module, exports, __webpack_require__) { 1629 | 1630 | "use strict"; 1631 | 1632 | 1633 | Object.defineProperty(exports, "__esModule", { 1634 | value: true 1635 | }); 1636 | exports.toTex = exports.print = exports.parse = undefined; 1637 | 1638 | var _parse = __webpack_require__(1); 1639 | 1640 | var _parse2 = _interopRequireDefault(_parse); 1641 | 1642 | var _print = __webpack_require__(2); 1643 | 1644 | var _print2 = _interopRequireDefault(_print); 1645 | 1646 | var _toTex = __webpack_require__(3); 1647 | 1648 | var _toTex2 = _interopRequireDefault(_toTex); 1649 | 1650 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 1651 | 1652 | exports.parse = _parse2.default; 1653 | exports.print = _print2.default; 1654 | exports.toTex = _toTex2.default; 1655 | 1656 | /***/ }) 1657 | /******/ ]); -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import parse from './lib/parse' 2 | import print from './lib/print' 3 | import toTex from './lib/toTex' 4 | 5 | export { 6 | parse, 7 | print, 8 | toTex, 9 | } 10 | -------------------------------------------------------------------------------- /lib/__test__/__snapshots__/parser.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Parser.parse abs ||a - b| - |b - c|| 1`] = ` 4 | { 5 | "type": "Apply", 6 | "op": "abs", 7 | "args": [ 8 | { 9 | "type": "Apply", 10 | "op": "add", 11 | "args": [ 12 | { 13 | "type": "Apply", 14 | "op": "abs", 15 | "args": [ 16 | { 17 | "type": "Apply", 18 | "op": "add", 19 | "args": [ 20 | { 21 | "type": "Identifier", 22 | "name": "a" 23 | }, 24 | { 25 | "wasMinus": true, 26 | "type": "Apply", 27 | "op": "neg", 28 | "args": [ 29 | { 30 | "type": "Identifier", 31 | "name": "b" 32 | } 33 | ] 34 | } 35 | ] 36 | } 37 | ] 38 | }, 39 | { 40 | "wasMinus": true, 41 | "type": "Apply", 42 | "op": "neg", 43 | "args": [ 44 | { 45 | "type": "Apply", 46 | "op": "abs", 47 | "args": [ 48 | { 49 | "type": "Apply", 50 | "op": "add", 51 | "args": [ 52 | { 53 | "type": "Identifier", 54 | "name": "b" 55 | }, 56 | { 57 | "wasMinus": true, 58 | "type": "Apply", 59 | "op": "neg", 60 | "args": [ 61 | { 62 | "type": "Identifier", 63 | "name": "c" 64 | } 65 | ] 66 | } 67 | ] 68 | } 69 | ] 70 | } 71 | ] 72 | } 73 | ] 74 | } 75 | ] 76 | } 77 | `; 78 | 79 | exports[`Parser.parse abs |a - b| 1`] = ` 80 | { 81 | "type": "Apply", 82 | "op": "abs", 83 | "args": [ 84 | { 85 | "type": "Apply", 86 | "op": "add", 87 | "args": [ 88 | { 89 | "type": "Identifier", 90 | "name": "a" 91 | }, 92 | { 93 | "wasMinus": true, 94 | "type": "Apply", 95 | "op": "neg", 96 | "args": [ 97 | { 98 | "type": "Identifier", 99 | "name": "b" 100 | } 101 | ] 102 | } 103 | ] 104 | } 105 | ] 106 | } 107 | `; 108 | 109 | exports[`Parser.parse abs abs(-1) 1`] = ` 110 | { 111 | "type": "Apply", 112 | "op": "abs", 113 | "args": [ 114 | { 115 | "type": "Apply", 116 | "op": "neg", 117 | "args": [ 118 | { 119 | "value": "1", 120 | "type": "Number" 121 | } 122 | ] 123 | } 124 | ] 125 | } 126 | `; 127 | 128 | exports[`Parser.parse addition/subtraction 1 - -2 1`] = ` 129 | { 130 | "type": "Apply", 131 | "op": "add", 132 | "args": [ 133 | { 134 | "value": "1", 135 | "type": "Number" 136 | }, 137 | { 138 | "wasMinus": true, 139 | "type": "Apply", 140 | "op": "neg", 141 | "args": [ 142 | { 143 | "type": "Apply", 144 | "op": "neg", 145 | "args": [ 146 | { 147 | "value": "2", 148 | "type": "Number" 149 | } 150 | ] 151 | } 152 | ] 153 | } 154 | ] 155 | } 156 | `; 157 | 158 | exports[`Parser.parse addition/subtraction 1 - 2 1`] = ` 159 | { 160 | "type": "Apply", 161 | "op": "add", 162 | "args": [ 163 | { 164 | "value": "1", 165 | "type": "Number" 166 | }, 167 | { 168 | "wasMinus": true, 169 | "type": "Apply", 170 | "op": "neg", 171 | "args": [ 172 | { 173 | "value": "2", 174 | "type": "Number" 175 | } 176 | ] 177 | } 178 | ] 179 | } 180 | `; 181 | 182 | exports[`Parser.parse addition/subtraction a + -b - c 1`] = ` 183 | { 184 | "type": "Apply", 185 | "op": "add", 186 | "args": [ 187 | { 188 | "type": "Identifier", 189 | "name": "a" 190 | }, 191 | { 192 | "type": "Apply", 193 | "op": "neg", 194 | "args": [ 195 | { 196 | "type": "Identifier", 197 | "name": "b" 198 | } 199 | ] 200 | }, 201 | { 202 | "wasMinus": true, 203 | "type": "Apply", 204 | "op": "neg", 205 | "args": [ 206 | { 207 | "type": "Identifier", 208 | "name": "c" 209 | } 210 | ] 211 | } 212 | ] 213 | } 214 | `; 215 | 216 | exports[`Parser.parse addition/subtraction a + b + c 1`] = ` 217 | { 218 | "type": "Apply", 219 | "op": "add", 220 | "args": [ 221 | { 222 | "type": "Identifier", 223 | "name": "a" 224 | }, 225 | { 226 | "type": "Identifier", 227 | "name": "b" 228 | }, 229 | { 230 | "type": "Identifier", 231 | "name": "c" 232 | } 233 | ] 234 | } 235 | `; 236 | 237 | exports[`Parser.parse addition/subtraction a - b - -c 1`] = ` 238 | { 239 | "type": "Apply", 240 | "op": "add", 241 | "args": [ 242 | { 243 | "type": "Identifier", 244 | "name": "a" 245 | }, 246 | { 247 | "wasMinus": true, 248 | "type": "Apply", 249 | "op": "neg", 250 | "args": [ 251 | { 252 | "type": "Identifier", 253 | "name": "b" 254 | } 255 | ] 256 | }, 257 | { 258 | "wasMinus": true, 259 | "type": "Apply", 260 | "op": "neg", 261 | "args": [ 262 | { 263 | "type": "Apply", 264 | "op": "neg", 265 | "args": [ 266 | { 267 | "type": "Identifier", 268 | "name": "c" 269 | } 270 | ] 271 | } 272 | ] 273 | } 274 | ] 275 | } 276 | `; 277 | 278 | exports[`Parser.parse addition/subtraction a - b - c 1`] = ` 279 | { 280 | "type": "Apply", 281 | "op": "add", 282 | "args": [ 283 | { 284 | "type": "Identifier", 285 | "name": "a" 286 | }, 287 | { 288 | "wasMinus": true, 289 | "type": "Apply", 290 | "op": "neg", 291 | "args": [ 292 | { 293 | "type": "Identifier", 294 | "name": "b" 295 | } 296 | ] 297 | }, 298 | { 299 | "wasMinus": true, 300 | "type": "Apply", 301 | "op": "neg", 302 | "args": [ 303 | { 304 | "type": "Identifier", 305 | "name": "c" 306 | } 307 | ] 308 | } 309 | ] 310 | } 311 | `; 312 | 313 | exports[`Parser.parse division (a*b)/(c*d) 1`] = ` 314 | { 315 | "type": "Apply", 316 | "op": "div", 317 | "args": [ 318 | { 319 | "type": "Apply", 320 | "op": "mul", 321 | "args": [ 322 | { 323 | "type": "Identifier", 324 | "name": "a" 325 | }, 326 | { 327 | "type": "Identifier", 328 | "name": "b" 329 | } 330 | ] 331 | }, 332 | { 333 | "type": "Apply", 334 | "op": "mul", 335 | "args": [ 336 | { 337 | "type": "Identifier", 338 | "name": "c" 339 | }, 340 | { 341 | "type": "Identifier", 342 | "name": "d" 343 | } 344 | ] 345 | } 346 | ] 347 | } 348 | `; 349 | 350 | exports[`Parser.parse division 2(x+1)/4 1`] = ` 351 | { 352 | "type": "Apply", 353 | "op": "mul", 354 | "implicit": true, 355 | "args": [ 356 | { 357 | "value": "2", 358 | "type": "Number" 359 | }, 360 | { 361 | "type": "Apply", 362 | "op": "div", 363 | "args": [ 364 | { 365 | "type": "Apply", 366 | "op": "add", 367 | "args": [ 368 | { 369 | "type": "Identifier", 370 | "name": "x" 371 | }, 372 | { 373 | "value": "1", 374 | "type": "Number" 375 | } 376 | ] 377 | }, 378 | { 379 | "value": "4", 380 | "type": "Number" 381 | } 382 | ] 383 | } 384 | ] 385 | } 386 | `; 387 | 388 | exports[`Parser.parse division 2x/4 1`] = ` 389 | { 390 | "type": "Apply", 391 | "op": "mul", 392 | "implicit": true, 393 | "args": [ 394 | { 395 | "value": "2", 396 | "type": "Number" 397 | }, 398 | { 399 | "type": "Apply", 400 | "op": "div", 401 | "args": [ 402 | { 403 | "type": "Identifier", 404 | "name": "x" 405 | }, 406 | { 407 | "value": "4", 408 | "type": "Number" 409 | } 410 | ] 411 | } 412 | ] 413 | } 414 | `; 415 | 416 | exports[`Parser.parse division a b c/d 1`] = ` 417 | { 418 | "type": "Apply", 419 | "op": "mul", 420 | "implicit": true, 421 | "args": [ 422 | { 423 | "type": "Identifier", 424 | "name": "a" 425 | }, 426 | { 427 | "type": "Identifier", 428 | "name": "b" 429 | }, 430 | { 431 | "type": "Apply", 432 | "op": "div", 433 | "args": [ 434 | { 435 | "type": "Identifier", 436 | "name": "c" 437 | }, 438 | { 439 | "type": "Identifier", 440 | "name": "d" 441 | } 442 | ] 443 | } 444 | ] 445 | } 446 | `; 447 | 448 | exports[`Parser.parse division a*b*c/d 1`] = ` 449 | { 450 | "type": "Apply", 451 | "op": "mul", 452 | "args": [ 453 | { 454 | "type": "Identifier", 455 | "name": "a" 456 | }, 457 | { 458 | "type": "Identifier", 459 | "name": "b" 460 | }, 461 | { 462 | "type": "Apply", 463 | "op": "div", 464 | "args": [ 465 | { 466 | "type": "Identifier", 467 | "name": "c" 468 | }, 469 | { 470 | "type": "Identifier", 471 | "name": "d" 472 | } 473 | ] 474 | } 475 | ] 476 | } 477 | `; 478 | 479 | exports[`Parser.parse division a/b*c/d 1`] = ` 480 | { 481 | "type": "Apply", 482 | "op": "mul", 483 | "args": [ 484 | { 485 | "type": "Apply", 486 | "op": "div", 487 | "args": [ 488 | { 489 | "type": "Identifier", 490 | "name": "a" 491 | }, 492 | { 493 | "type": "Identifier", 494 | "name": "b" 495 | } 496 | ] 497 | }, 498 | { 499 | "type": "Apply", 500 | "op": "div", 501 | "args": [ 502 | { 503 | "type": "Identifier", 504 | "name": "c" 505 | }, 506 | { 507 | "type": "Identifier", 508 | "name": "d" 509 | } 510 | ] 511 | } 512 | ] 513 | } 514 | `; 515 | 516 | exports[`Parser.parse division a/b/c 1`] = ` 517 | { 518 | "type": "Apply", 519 | "op": "div", 520 | "args": [ 521 | { 522 | "type": "Apply", 523 | "op": "div", 524 | "args": [ 525 | { 526 | "type": "Identifier", 527 | "name": "a" 528 | }, 529 | { 530 | "type": "Identifier", 531 | "name": "b" 532 | } 533 | ] 534 | }, 535 | { 536 | "type": "Identifier", 537 | "name": "c" 538 | } 539 | ] 540 | } 541 | `; 542 | 543 | exports[`Parser.parse division a^2/b^2 1`] = ` 544 | { 545 | "type": "Apply", 546 | "op": "div", 547 | "args": [ 548 | { 549 | "type": "Apply", 550 | "op": "pow", 551 | "args": [ 552 | { 553 | "type": "Identifier", 554 | "name": "a" 555 | }, 556 | { 557 | "value": "2", 558 | "type": "Number" 559 | } 560 | ] 561 | }, 562 | { 563 | "type": "Apply", 564 | "op": "pow", 565 | "args": [ 566 | { 567 | "type": "Identifier", 568 | "name": "b" 569 | }, 570 | { 571 | "value": "2", 572 | "type": "Number" 573 | } 574 | ] 575 | } 576 | ] 577 | } 578 | `; 579 | 580 | exports[`Parser.parse ellipsis #a_0#x + ... + #a_n#x 1`] = ` 581 | { 582 | "type": "Apply", 583 | "op": "add", 584 | "args": [ 585 | { 586 | "type": "Apply", 587 | "op": "mul", 588 | "implicit": true, 589 | "args": [ 590 | { 591 | "type": "Placeholder", 592 | "subscript": { 593 | "value": "0", 594 | "type": "Number" 595 | }, 596 | "name": "a" 597 | }, 598 | { 599 | "type": "Placeholder", 600 | "name": "x" 601 | } 602 | ] 603 | }, 604 | { 605 | "type": "Ellipsis" 606 | }, 607 | { 608 | "type": "Apply", 609 | "op": "mul", 610 | "implicit": true, 611 | "args": [ 612 | { 613 | "type": "Placeholder", 614 | "subscript": { 615 | "type": "Identifier", 616 | "name": "n" 617 | }, 618 | "name": "a" 619 | }, 620 | { 621 | "type": "Placeholder", 622 | "name": "x" 623 | } 624 | ] 625 | } 626 | ] 627 | } 628 | `; 629 | 630 | exports[`Parser.parse ellipsis 1 * ... * n 1`] = ` 631 | { 632 | "type": "Apply", 633 | "op": "mul", 634 | "args": [ 635 | { 636 | "value": "1", 637 | "type": "Number" 638 | }, 639 | { 640 | "type": "Ellipsis" 641 | }, 642 | { 643 | "type": "Identifier", 644 | "name": "n" 645 | } 646 | ] 647 | } 648 | `; 649 | 650 | exports[`Parser.parse ellipsis 1, 2, ..., n 1`] = ` 651 | { 652 | "type": "Sequence", 653 | "items": [ 654 | { 655 | "value": "1", 656 | "type": "Number" 657 | }, 658 | { 659 | "value": "2", 660 | "type": "Number" 661 | }, 662 | { 663 | "type": "Ellipsis" 664 | }, 665 | { 666 | "type": "Identifier", 667 | "name": "n" 668 | } 669 | ] 670 | } 671 | `; 672 | 673 | exports[`Parser.parse ellipsis a + ... + z 1`] = ` 674 | { 675 | "type": "Apply", 676 | "op": "add", 677 | "args": [ 678 | { 679 | "type": "Identifier", 680 | "name": "a" 681 | }, 682 | { 683 | "type": "Ellipsis" 684 | }, 685 | { 686 | "type": "Identifier", 687 | "name": "z" 688 | } 689 | ] 690 | } 691 | `; 692 | 693 | exports[`Parser.parse factorial (2 * n)! 1`] = ` 694 | { 695 | "type": "Apply", 696 | "op": "fact", 697 | "args": [ 698 | { 699 | "type": "Apply", 700 | "op": "mul", 701 | "args": [ 702 | { 703 | "value": "2", 704 | "type": "Number" 705 | }, 706 | { 707 | "type": "Identifier", 708 | "name": "n" 709 | } 710 | ] 711 | } 712 | ] 713 | } 714 | `; 715 | 716 | exports[`Parser.parse factorial (5^2)! 1`] = ` 717 | { 718 | "type": "Apply", 719 | "op": "fact", 720 | "args": [ 721 | { 722 | "type": "Apply", 723 | "op": "pow", 724 | "args": [ 725 | { 726 | "value": "5", 727 | "type": "Number" 728 | }, 729 | { 730 | "value": "2", 731 | "type": "Number" 732 | } 733 | ] 734 | } 735 | ] 736 | } 737 | `; 738 | 739 | exports[`Parser.parse factorial 2 * n! 1`] = ` 740 | { 741 | "type": "Apply", 742 | "op": "mul", 743 | "args": [ 744 | { 745 | "value": "2", 746 | "type": "Number" 747 | }, 748 | { 749 | "type": "Apply", 750 | "op": "fact", 751 | "args": [ 752 | { 753 | "type": "Identifier", 754 | "name": "n" 755 | } 756 | ] 757 | } 758 | ] 759 | } 760 | `; 761 | 762 | exports[`Parser.parse factorial 5! 1`] = ` 763 | { 764 | "type": "Apply", 765 | "op": "fact", 766 | "args": [ 767 | { 768 | "value": "5", 769 | "type": "Number" 770 | } 771 | ] 772 | } 773 | `; 774 | 775 | exports[`Parser.parse factorial 5!^2 1`] = ` 776 | { 777 | "type": "Apply", 778 | "op": "pow", 779 | "args": [ 780 | { 781 | "type": "Apply", 782 | "op": "fact", 783 | "args": [ 784 | { 785 | "value": "5", 786 | "type": "Number" 787 | } 788 | ] 789 | }, 790 | { 791 | "value": "2", 792 | "type": "Number" 793 | } 794 | ] 795 | } 796 | `; 797 | 798 | exports[`Parser.parse factorial x! 1`] = ` 799 | { 800 | "type": "Apply", 801 | "op": "fact", 802 | "args": [ 803 | { 804 | "type": "Identifier", 805 | "name": "x" 806 | } 807 | ] 808 | } 809 | `; 810 | 811 | exports[`Parser.parse factorial x^2! 1`] = ` 812 | { 813 | "type": "Apply", 814 | "op": "pow", 815 | "args": [ 816 | { 817 | "type": "Identifier", 818 | "name": "x" 819 | }, 820 | { 821 | "type": "Apply", 822 | "op": "fact", 823 | "args": [ 824 | { 825 | "value": "2", 826 | "type": "Number" 827 | } 828 | ] 829 | } 830 | ] 831 | } 832 | `; 833 | 834 | exports[`Parser.parse functions f(a+b) 1`] = ` 835 | { 836 | "type": "Apply", 837 | "op": { 838 | "type": "Identifier", 839 | "name": "f" 840 | }, 841 | "args": [ 842 | { 843 | "type": "Apply", 844 | "op": "add", 845 | "args": [ 846 | { 847 | "type": "Identifier", 848 | "name": "a" 849 | }, 850 | { 851 | "type": "Identifier", 852 | "name": "b" 853 | } 854 | ] 855 | } 856 | ] 857 | } 858 | `; 859 | 860 | exports[`Parser.parse functions f(a,b) 1`] = ` 861 | { 862 | "type": "Apply", 863 | "op": { 864 | "type": "Identifier", 865 | "name": "f" 866 | }, 867 | "args": [ 868 | { 869 | "type": "Identifier", 870 | "name": "a" 871 | }, 872 | { 873 | "type": "Identifier", 874 | "name": "b" 875 | } 876 | ] 877 | } 878 | `; 879 | 880 | exports[`Parser.parse functions f(f(a)) 1`] = ` 881 | { 882 | "type": "Apply", 883 | "op": { 884 | "type": "Identifier", 885 | "name": "f" 886 | }, 887 | "args": [ 888 | { 889 | "type": "Apply", 890 | "op": { 891 | "type": "Identifier", 892 | "name": "f" 893 | }, 894 | "args": [ 895 | { 896 | "type": "Identifier", 897 | "name": "a" 898 | } 899 | ] 900 | } 901 | ] 902 | } 903 | `; 904 | 905 | exports[`Parser.parse integrals int x^2 dx 1`] = ` 906 | { 907 | "type": "Apply", 908 | "op": "int", 909 | "args": [ 910 | { 911 | "type": "Apply", 912 | "op": "pow", 913 | "args": [ 914 | { 915 | "type": "Identifier", 916 | "name": "x" 917 | }, 918 | { 919 | "value": "2", 920 | "type": "Number" 921 | } 922 | ] 923 | }, 924 | { 925 | "type": "Identifier", 926 | "name": "dx" 927 | } 928 | ] 929 | } 930 | `; 931 | 932 | exports[`Parser.parse integrals int_0^1 int_0^1 x^2 + y^2 dx dy 1`] = ` 933 | { 934 | "type": "Apply", 935 | "op": "int", 936 | "limits": [ 937 | { 938 | "value": "0", 939 | "type": "Number" 940 | }, 941 | { 942 | "value": "1", 943 | "type": "Number" 944 | } 945 | ], 946 | "args": [ 947 | { 948 | "type": "Apply", 949 | "op": "int", 950 | "limits": [ 951 | { 952 | "value": "0", 953 | "type": "Number" 954 | }, 955 | { 956 | "value": "1", 957 | "type": "Number" 958 | } 959 | ], 960 | "args": [ 961 | { 962 | "type": "Apply", 963 | "op": "add", 964 | "args": [ 965 | { 966 | "type": "Apply", 967 | "op": "pow", 968 | "args": [ 969 | { 970 | "type": "Identifier", 971 | "name": "x" 972 | }, 973 | { 974 | "value": "2", 975 | "type": "Number" 976 | } 977 | ] 978 | }, 979 | { 980 | "type": "Apply", 981 | "op": "pow", 982 | "args": [ 983 | { 984 | "type": "Identifier", 985 | "name": "y" 986 | }, 987 | { 988 | "value": "2", 989 | "type": "Number" 990 | } 991 | ] 992 | } 993 | ] 994 | }, 995 | { 996 | "type": "Identifier", 997 | "name": "dx" 998 | } 999 | ] 1000 | }, 1001 | { 1002 | "type": "Identifier", 1003 | "name": "dy" 1004 | } 1005 | ] 1006 | } 1007 | `; 1008 | 1009 | exports[`Parser.parse integrals int_0^1 int_0^1 x^2 y^2 dx dy 1`] = ` 1010 | { 1011 | "type": "Apply", 1012 | "op": "int", 1013 | "limits": [ 1014 | { 1015 | "value": "0", 1016 | "type": "Number" 1017 | }, 1018 | { 1019 | "value": "1", 1020 | "type": "Number" 1021 | } 1022 | ], 1023 | "args": [ 1024 | { 1025 | "type": "Apply", 1026 | "op": "int", 1027 | "limits": [ 1028 | { 1029 | "value": "0", 1030 | "type": "Number" 1031 | }, 1032 | { 1033 | "value": "1", 1034 | "type": "Number" 1035 | } 1036 | ], 1037 | "args": [ 1038 | { 1039 | "type": "Apply", 1040 | "op": "mul", 1041 | "implicit": true, 1042 | "args": [ 1043 | { 1044 | "type": "Apply", 1045 | "op": "pow", 1046 | "args": [ 1047 | { 1048 | "type": "Identifier", 1049 | "name": "x" 1050 | }, 1051 | { 1052 | "value": "2", 1053 | "type": "Number" 1054 | } 1055 | ] 1056 | }, 1057 | { 1058 | "type": "Apply", 1059 | "op": "pow", 1060 | "args": [ 1061 | { 1062 | "type": "Identifier", 1063 | "name": "y" 1064 | }, 1065 | { 1066 | "value": "2", 1067 | "type": "Number" 1068 | } 1069 | ] 1070 | } 1071 | ] 1072 | }, 1073 | { 1074 | "type": "Identifier", 1075 | "name": "dx" 1076 | } 1077 | ] 1078 | }, 1079 | { 1080 | "type": "Identifier", 1081 | "name": "dy" 1082 | } 1083 | ] 1084 | } 1085 | `; 1086 | 1087 | exports[`Parser.parse integrals int_0^1 x^2 2x dx 1`] = ` 1088 | { 1089 | "type": "Apply", 1090 | "op": "int", 1091 | "limits": [ 1092 | { 1093 | "value": "0", 1094 | "type": "Number" 1095 | }, 1096 | { 1097 | "value": "1", 1098 | "type": "Number" 1099 | } 1100 | ], 1101 | "args": [ 1102 | { 1103 | "type": "Apply", 1104 | "op": "mul", 1105 | "implicit": true, 1106 | "args": [ 1107 | { 1108 | "type": "Apply", 1109 | "op": "pow", 1110 | "args": [ 1111 | { 1112 | "type": "Identifier", 1113 | "name": "x" 1114 | }, 1115 | { 1116 | "value": "2", 1117 | "type": "Number" 1118 | } 1119 | ] 1120 | }, 1121 | { 1122 | "value": "2", 1123 | "type": "Number" 1124 | }, 1125 | { 1126 | "type": "Identifier", 1127 | "name": "x" 1128 | } 1129 | ] 1130 | }, 1131 | { 1132 | "type": "Identifier", 1133 | "name": "dx" 1134 | } 1135 | ] 1136 | } 1137 | `; 1138 | 1139 | exports[`Parser.parse integrals int_0^1 x^2 dx 1`] = ` 1140 | { 1141 | "type": "Apply", 1142 | "op": "int", 1143 | "limits": [ 1144 | { 1145 | "value": "0", 1146 | "type": "Number" 1147 | }, 1148 | { 1149 | "value": "1", 1150 | "type": "Number" 1151 | } 1152 | ], 1153 | "args": [ 1154 | { 1155 | "type": "Apply", 1156 | "op": "pow", 1157 | "args": [ 1158 | { 1159 | "type": "Identifier", 1160 | "name": "x" 1161 | }, 1162 | { 1163 | "value": "2", 1164 | "type": "Number" 1165 | } 1166 | ] 1167 | }, 1168 | { 1169 | "type": "Identifier", 1170 | "name": "dx" 1171 | } 1172 | ] 1173 | } 1174 | `; 1175 | 1176 | exports[`Parser.parse multiplication (a)(b)(c) 1`] = ` 1177 | { 1178 | "type": "Apply", 1179 | "op": "mul", 1180 | "implicit": true, 1181 | "args": [ 1182 | { 1183 | "type": "Parentheses", 1184 | "body": { 1185 | "type": "Identifier", 1186 | "name": "a" 1187 | } 1188 | }, 1189 | { 1190 | "type": "Parentheses", 1191 | "body": { 1192 | "type": "Identifier", 1193 | "name": "b" 1194 | } 1195 | }, 1196 | { 1197 | "type": "Parentheses", 1198 | "body": { 1199 | "type": "Identifier", 1200 | "name": "c" 1201 | } 1202 | } 1203 | ] 1204 | } 1205 | `; 1206 | 1207 | exports[`Parser.parse multiplication (a)b(c) 1`] = ` 1208 | { 1209 | "type": "Apply", 1210 | "op": "mul", 1211 | "implicit": true, 1212 | "args": [ 1213 | { 1214 | "type": "Parentheses", 1215 | "body": { 1216 | "type": "Identifier", 1217 | "name": "a" 1218 | } 1219 | }, 1220 | { 1221 | "type": "Apply", 1222 | "op": { 1223 | "type": "Identifier", 1224 | "name": "b" 1225 | }, 1226 | "args": [ 1227 | { 1228 | "type": "Identifier", 1229 | "name": "c" 1230 | } 1231 | ] 1232 | } 1233 | ] 1234 | } 1235 | `; 1236 | 1237 | exports[`Parser.parse multiplication a b * b * b c 1`] = ` 1238 | { 1239 | "type": "Apply", 1240 | "op": "mul", 1241 | "args": [ 1242 | { 1243 | "type": "Apply", 1244 | "op": "mul", 1245 | "implicit": true, 1246 | "args": [ 1247 | { 1248 | "type": "Identifier", 1249 | "name": "a" 1250 | }, 1251 | { 1252 | "type": "Identifier", 1253 | "name": "b" 1254 | } 1255 | ] 1256 | }, 1257 | { 1258 | "type": "Identifier", 1259 | "name": "b" 1260 | }, 1261 | { 1262 | "type": "Apply", 1263 | "op": "mul", 1264 | "implicit": true, 1265 | "args": [ 1266 | { 1267 | "type": "Identifier", 1268 | "name": "b" 1269 | }, 1270 | { 1271 | "type": "Identifier", 1272 | "name": "c" 1273 | } 1274 | ] 1275 | } 1276 | ] 1277 | } 1278 | `; 1279 | 1280 | exports[`Parser.parse multiplication a b c 1`] = ` 1281 | { 1282 | "type": "Apply", 1283 | "op": "mul", 1284 | "implicit": true, 1285 | "args": [ 1286 | { 1287 | "type": "Identifier", 1288 | "name": "a" 1289 | }, 1290 | { 1291 | "type": "Identifier", 1292 | "name": "b" 1293 | }, 1294 | { 1295 | "type": "Identifier", 1296 | "name": "c" 1297 | } 1298 | ] 1299 | } 1300 | `; 1301 | 1302 | exports[`Parser.parse multiplication a*b c 1`] = ` 1303 | { 1304 | "type": "Apply", 1305 | "op": "mul", 1306 | "args": [ 1307 | { 1308 | "type": "Identifier", 1309 | "name": "a" 1310 | }, 1311 | { 1312 | "type": "Apply", 1313 | "op": "mul", 1314 | "implicit": true, 1315 | "args": [ 1316 | { 1317 | "type": "Identifier", 1318 | "name": "b" 1319 | }, 1320 | { 1321 | "type": "Identifier", 1322 | "name": "c" 1323 | } 1324 | ] 1325 | } 1326 | ] 1327 | } 1328 | `; 1329 | 1330 | exports[`Parser.parse multiplication a*b*c 1`] = ` 1331 | { 1332 | "type": "Apply", 1333 | "op": "mul", 1334 | "args": [ 1335 | { 1336 | "type": "Identifier", 1337 | "name": "a" 1338 | }, 1339 | { 1340 | "type": "Identifier", 1341 | "name": "b" 1342 | }, 1343 | { 1344 | "type": "Identifier", 1345 | "name": "c" 1346 | } 1347 | ] 1348 | } 1349 | `; 1350 | 1351 | exports[`Parser.parse nthRoot nthRoot(-27, 3) 1`] = ` 1352 | { 1353 | "type": "Apply", 1354 | "op": "nthRoot", 1355 | "args": [ 1356 | { 1357 | "type": "Apply", 1358 | "op": "neg", 1359 | "args": [ 1360 | { 1361 | "value": "27", 1362 | "type": "Number" 1363 | } 1364 | ] 1365 | }, 1366 | { 1367 | "value": "3", 1368 | "type": "Number" 1369 | } 1370 | ] 1371 | } 1372 | `; 1373 | 1374 | exports[`Parser.parse nthRoot nthRoot(x) 1`] = ` 1375 | { 1376 | "type": "Apply", 1377 | "op": "nthRoot", 1378 | "args": [ 1379 | { 1380 | "type": "Identifier", 1381 | "name": "x" 1382 | }, 1383 | { 1384 | "value": "2", 1385 | "type": "Number" 1386 | } 1387 | ] 1388 | } 1389 | `; 1390 | 1391 | exports[`Parser.parse nthRoot nthRoot(x, 2) 1`] = ` 1392 | { 1393 | "type": "Apply", 1394 | "op": "nthRoot", 1395 | "args": [ 1396 | { 1397 | "type": "Identifier", 1398 | "name": "x" 1399 | }, 1400 | { 1401 | "value": "2", 1402 | "type": "Number" 1403 | } 1404 | ] 1405 | } 1406 | `; 1407 | 1408 | exports[`Parser.parse parentheses ((1 + 2)) 1`] = ` 1409 | { 1410 | "type": "Parentheses", 1411 | "body": { 1412 | "type": "Parentheses", 1413 | "body": { 1414 | "type": "Apply", 1415 | "op": "add", 1416 | "args": [ 1417 | { 1418 | "value": "1", 1419 | "type": "Number" 1420 | }, 1421 | { 1422 | "value": "2", 1423 | "type": "Number" 1424 | } 1425 | ] 1426 | } 1427 | } 1428 | } 1429 | `; 1430 | 1431 | exports[`Parser.parse parentheses ((a * b)) 1`] = ` 1432 | { 1433 | "type": "Parentheses", 1434 | "body": { 1435 | "type": "Parentheses", 1436 | "body": { 1437 | "type": "Apply", 1438 | "op": "mul", 1439 | "args": [ 1440 | { 1441 | "type": "Identifier", 1442 | "name": "a" 1443 | }, 1444 | { 1445 | "type": "Identifier", 1446 | "name": "b" 1447 | } 1448 | ] 1449 | } 1450 | } 1451 | } 1452 | `; 1453 | 1454 | exports[`Parser.parse parentheses (1 + 2) 1`] = ` 1455 | { 1456 | "type": "Parentheses", 1457 | "body": { 1458 | "type": "Apply", 1459 | "op": "add", 1460 | "args": [ 1461 | { 1462 | "value": "1", 1463 | "type": "Number" 1464 | }, 1465 | { 1466 | "value": "2", 1467 | "type": "Number" 1468 | } 1469 | ] 1470 | } 1471 | } 1472 | `; 1473 | 1474 | exports[`Parser.parse parentheses (a * b) 1`] = ` 1475 | { 1476 | "type": "Parentheses", 1477 | "body": { 1478 | "type": "Apply", 1479 | "op": "mul", 1480 | "args": [ 1481 | { 1482 | "type": "Identifier", 1483 | "name": "a" 1484 | }, 1485 | { 1486 | "type": "Identifier", 1487 | "name": "b" 1488 | } 1489 | ] 1490 | } 1491 | } 1492 | `; 1493 | 1494 | exports[`Parser.parse parentheses (a)(b) 1`] = ` 1495 | { 1496 | "type": "Apply", 1497 | "op": "mul", 1498 | "implicit": true, 1499 | "args": [ 1500 | { 1501 | "type": "Parentheses", 1502 | "body": { 1503 | "type": "Identifier", 1504 | "name": "a" 1505 | } 1506 | }, 1507 | { 1508 | "type": "Parentheses", 1509 | "body": { 1510 | "type": "Identifier", 1511 | "name": "b" 1512 | } 1513 | } 1514 | ] 1515 | } 1516 | `; 1517 | 1518 | exports[`Parser.parse parentheses (x + y) - (a + b) 1`] = ` 1519 | { 1520 | "type": "Apply", 1521 | "op": "add", 1522 | "args": [ 1523 | { 1524 | "type": "Parentheses", 1525 | "body": { 1526 | "type": "Apply", 1527 | "op": "add", 1528 | "args": [ 1529 | { 1530 | "type": "Identifier", 1531 | "name": "x" 1532 | }, 1533 | { 1534 | "type": "Identifier", 1535 | "name": "y" 1536 | } 1537 | ] 1538 | } 1539 | }, 1540 | { 1541 | "wasMinus": true, 1542 | "type": "Apply", 1543 | "op": "neg", 1544 | "args": [ 1545 | { 1546 | "type": "Apply", 1547 | "op": "add", 1548 | "args": [ 1549 | { 1550 | "type": "Identifier", 1551 | "name": "a" 1552 | }, 1553 | { 1554 | "type": "Identifier", 1555 | "name": "b" 1556 | } 1557 | ] 1558 | } 1559 | ] 1560 | } 1561 | ] 1562 | } 1563 | `; 1564 | 1565 | exports[`Parser.parse parentheses 5 + ((3 * 6)) 1`] = ` 1566 | { 1567 | "type": "Apply", 1568 | "op": "add", 1569 | "args": [ 1570 | { 1571 | "value": "5", 1572 | "type": "Number" 1573 | }, 1574 | { 1575 | "type": "Parentheses", 1576 | "body": { 1577 | "type": "Parentheses", 1578 | "body": { 1579 | "type": "Apply", 1580 | "op": "mul", 1581 | "args": [ 1582 | { 1583 | "value": "3", 1584 | "type": "Number" 1585 | }, 1586 | { 1587 | "value": "6", 1588 | "type": "Number" 1589 | } 1590 | ] 1591 | } 1592 | } 1593 | } 1594 | ] 1595 | } 1596 | `; 1597 | 1598 | exports[`Parser.parse parentheses 5 + (3 * 6) 1`] = ` 1599 | { 1600 | "type": "Apply", 1601 | "op": "add", 1602 | "args": [ 1603 | { 1604 | "value": "5", 1605 | "type": "Number" 1606 | }, 1607 | { 1608 | "type": "Parentheses", 1609 | "body": { 1610 | "type": "Apply", 1611 | "op": "mul", 1612 | "args": [ 1613 | { 1614 | "value": "3", 1615 | "type": "Number" 1616 | }, 1617 | { 1618 | "value": "6", 1619 | "type": "Number" 1620 | } 1621 | ] 1622 | } 1623 | } 1624 | ] 1625 | } 1626 | `; 1627 | 1628 | exports[`Parser.parse parentheses a * (b + c) 1`] = ` 1629 | { 1630 | "type": "Apply", 1631 | "op": "mul", 1632 | "args": [ 1633 | { 1634 | "type": "Identifier", 1635 | "name": "a" 1636 | }, 1637 | { 1638 | "type": "Apply", 1639 | "op": "add", 1640 | "args": [ 1641 | { 1642 | "type": "Identifier", 1643 | "name": "b" 1644 | }, 1645 | { 1646 | "type": "Identifier", 1647 | "name": "c" 1648 | } 1649 | ] 1650 | } 1651 | ] 1652 | } 1653 | `; 1654 | 1655 | exports[`Parser.parse placeholders #a #b / #c 1`] = ` 1656 | { 1657 | "type": "Apply", 1658 | "op": "mul", 1659 | "implicit": true, 1660 | "args": [ 1661 | { 1662 | "type": "Placeholder", 1663 | "name": "a" 1664 | }, 1665 | { 1666 | "type": "Apply", 1667 | "op": "div", 1668 | "args": [ 1669 | { 1670 | "type": "Placeholder", 1671 | "name": "b" 1672 | }, 1673 | { 1674 | "type": "Placeholder", 1675 | "name": "c" 1676 | } 1677 | ] 1678 | } 1679 | ] 1680 | } 1681 | `; 1682 | 1683 | exports[`Parser.parse placeholders #a #f(#x) 1`] = ` 1684 | { 1685 | "type": "Apply", 1686 | "op": "mul", 1687 | "implicit": true, 1688 | "args": [ 1689 | { 1690 | "type": "Placeholder", 1691 | "name": "a" 1692 | }, 1693 | { 1694 | "type": "Apply", 1695 | "op": { 1696 | "type": "Placeholder", 1697 | "name": "f" 1698 | }, 1699 | "args": [ 1700 | { 1701 | "type": "Placeholder", 1702 | "name": "x" 1703 | } 1704 | ] 1705 | } 1706 | ] 1707 | } 1708 | `; 1709 | 1710 | exports[`Parser.parse placeholders #a 1`] = ` 1711 | { 1712 | "type": "Placeholder", 1713 | "name": "a" 1714 | } 1715 | `; 1716 | 1717 | exports[`Parser.parse placeholders #eval(#a + #b) 1`] = ` 1718 | { 1719 | "type": "Apply", 1720 | "op": { 1721 | "type": "Placeholder", 1722 | "name": "eval" 1723 | }, 1724 | "args": [ 1725 | { 1726 | "type": "Apply", 1727 | "op": "add", 1728 | "args": [ 1729 | { 1730 | "type": "Placeholder", 1731 | "name": "a" 1732 | }, 1733 | { 1734 | "type": "Placeholder", 1735 | "name": "b" 1736 | } 1737 | ] 1738 | } 1739 | ] 1740 | } 1741 | `; 1742 | 1743 | exports[`Parser.parse placeholders #f(#x) 1`] = ` 1744 | { 1745 | "type": "Apply", 1746 | "op": { 1747 | "type": "Placeholder", 1748 | "name": "f" 1749 | }, 1750 | "args": [ 1751 | { 1752 | "type": "Placeholder", 1753 | "name": "x" 1754 | } 1755 | ] 1756 | } 1757 | `; 1758 | 1759 | exports[`Parser.parse powers (-2)^x 1`] = ` 1760 | { 1761 | "type": "Apply", 1762 | "op": "pow", 1763 | "args": [ 1764 | { 1765 | "type": "Apply", 1766 | "op": "neg", 1767 | "args": [ 1768 | { 1769 | "value": "2", 1770 | "type": "Number" 1771 | } 1772 | ] 1773 | }, 1774 | { 1775 | "type": "Identifier", 1776 | "name": "x" 1777 | } 1778 | ] 1779 | } 1780 | `; 1781 | 1782 | exports[`Parser.parse powers -1^-2 1`] = ` 1783 | { 1784 | "type": "Apply", 1785 | "op": "neg", 1786 | "args": [ 1787 | { 1788 | "type": "Apply", 1789 | "op": "pow", 1790 | "args": [ 1791 | { 1792 | "value": "1", 1793 | "type": "Number" 1794 | }, 1795 | { 1796 | "type": "Apply", 1797 | "op": "neg", 1798 | "args": [ 1799 | { 1800 | "value": "2", 1801 | "type": "Number" 1802 | } 1803 | ] 1804 | } 1805 | ] 1806 | } 1807 | ] 1808 | } 1809 | `; 1810 | 1811 | exports[`Parser.parse powers -a^-b 1`] = ` 1812 | { 1813 | "type": "Apply", 1814 | "op": "neg", 1815 | "args": [ 1816 | { 1817 | "type": "Apply", 1818 | "op": "pow", 1819 | "args": [ 1820 | { 1821 | "type": "Identifier", 1822 | "name": "a" 1823 | }, 1824 | { 1825 | "type": "Apply", 1826 | "op": "neg", 1827 | "args": [ 1828 | { 1829 | "type": "Identifier", 1830 | "name": "b" 1831 | } 1832 | ] 1833 | } 1834 | ] 1835 | } 1836 | ] 1837 | } 1838 | `; 1839 | 1840 | exports[`Parser.parse powers a^-1.23 1`] = ` 1841 | { 1842 | "type": "Apply", 1843 | "op": "pow", 1844 | "args": [ 1845 | { 1846 | "type": "Identifier", 1847 | "name": "a" 1848 | }, 1849 | { 1850 | "type": "Apply", 1851 | "op": "neg", 1852 | "args": [ 1853 | { 1854 | "value": "1.23", 1855 | "type": "Number" 1856 | } 1857 | ] 1858 | } 1859 | ] 1860 | } 1861 | `; 1862 | 1863 | exports[`Parser.parse powers a^b^c 1`] = ` 1864 | { 1865 | "type": "Apply", 1866 | "op": "pow", 1867 | "args": [ 1868 | { 1869 | "type": "Identifier", 1870 | "name": "a" 1871 | }, 1872 | { 1873 | "type": "Apply", 1874 | "op": "pow", 1875 | "args": [ 1876 | { 1877 | "type": "Identifier", 1878 | "name": "b" 1879 | }, 1880 | { 1881 | "type": "Identifier", 1882 | "name": "c" 1883 | } 1884 | ] 1885 | } 1886 | ] 1887 | } 1888 | `; 1889 | 1890 | exports[`Parser.parse relations (binary) a != b 1`] = ` 1891 | { 1892 | "type": "Apply", 1893 | "op": "ne", 1894 | "args": [ 1895 | { 1896 | "type": "Identifier", 1897 | "name": "a" 1898 | }, 1899 | { 1900 | "type": "Identifier", 1901 | "name": "b" 1902 | } 1903 | ] 1904 | } 1905 | `; 1906 | 1907 | exports[`Parser.parse relations (binary) a < b 1`] = ` 1908 | { 1909 | "type": "Apply", 1910 | "op": "lt", 1911 | "args": [ 1912 | { 1913 | "type": "Identifier", 1914 | "name": "a" 1915 | }, 1916 | { 1917 | "type": "Identifier", 1918 | "name": "b" 1919 | } 1920 | ] 1921 | } 1922 | `; 1923 | 1924 | exports[`Parser.parse relations (binary) a <= b 1`] = ` 1925 | { 1926 | "type": "Apply", 1927 | "op": "le", 1928 | "args": [ 1929 | { 1930 | "type": "Identifier", 1931 | "name": "a" 1932 | }, 1933 | { 1934 | "type": "Identifier", 1935 | "name": "b" 1936 | } 1937 | ] 1938 | } 1939 | `; 1940 | 1941 | exports[`Parser.parse relations (binary) a = b 1`] = ` 1942 | { 1943 | "type": "Apply", 1944 | "op": "eq", 1945 | "args": [ 1946 | { 1947 | "type": "Identifier", 1948 | "name": "a" 1949 | }, 1950 | { 1951 | "type": "Identifier", 1952 | "name": "b" 1953 | } 1954 | ] 1955 | } 1956 | `; 1957 | 1958 | exports[`Parser.parse relations (binary) a > b 1`] = ` 1959 | { 1960 | "type": "Apply", 1961 | "op": "gt", 1962 | "args": [ 1963 | { 1964 | "type": "Identifier", 1965 | "name": "a" 1966 | }, 1967 | { 1968 | "type": "Identifier", 1969 | "name": "b" 1970 | } 1971 | ] 1972 | } 1973 | `; 1974 | 1975 | exports[`Parser.parse relations (binary) a >= b 1`] = ` 1976 | { 1977 | "type": "Apply", 1978 | "op": "ge", 1979 | "args": [ 1980 | { 1981 | "type": "Identifier", 1982 | "name": "a" 1983 | }, 1984 | { 1985 | "type": "Identifier", 1986 | "name": "b" 1987 | } 1988 | ] 1989 | } 1990 | `; 1991 | 1992 | exports[`Parser.parse relations (n-ary) a != b != c 1`] = ` 1993 | { 1994 | "type": "Apply", 1995 | "op": "ne", 1996 | "args": [ 1997 | { 1998 | "type": "Identifier", 1999 | "name": "a" 2000 | }, 2001 | { 2002 | "type": "Identifier", 2003 | "name": "b" 2004 | }, 2005 | { 2006 | "type": "Identifier", 2007 | "name": "c" 2008 | } 2009 | ] 2010 | } 2011 | `; 2012 | 2013 | exports[`Parser.parse relations (n-ary) a + b != c + d != e + f 1`] = ` 2014 | { 2015 | "type": "Apply", 2016 | "op": "ne", 2017 | "args": [ 2018 | { 2019 | "type": "Apply", 2020 | "op": "add", 2021 | "args": [ 2022 | { 2023 | "type": "Identifier", 2024 | "name": "a" 2025 | }, 2026 | { 2027 | "type": "Identifier", 2028 | "name": "b" 2029 | } 2030 | ] 2031 | }, 2032 | { 2033 | "type": "Apply", 2034 | "op": "add", 2035 | "args": [ 2036 | { 2037 | "type": "Identifier", 2038 | "name": "c" 2039 | }, 2040 | { 2041 | "type": "Identifier", 2042 | "name": "d" 2043 | } 2044 | ] 2045 | }, 2046 | { 2047 | "type": "Apply", 2048 | "op": "add", 2049 | "args": [ 2050 | { 2051 | "type": "Identifier", 2052 | "name": "e" 2053 | }, 2054 | { 2055 | "type": "Identifier", 2056 | "name": "f" 2057 | } 2058 | ] 2059 | } 2060 | ] 2061 | } 2062 | `; 2063 | 2064 | exports[`Parser.parse relations (n-ary) a + b = c + d = e + f 1`] = ` 2065 | { 2066 | "type": "Apply", 2067 | "op": "eq", 2068 | "args": [ 2069 | { 2070 | "type": "Apply", 2071 | "op": "add", 2072 | "args": [ 2073 | { 2074 | "type": "Identifier", 2075 | "name": "a" 2076 | }, 2077 | { 2078 | "type": "Identifier", 2079 | "name": "b" 2080 | } 2081 | ] 2082 | }, 2083 | { 2084 | "type": "Apply", 2085 | "op": "add", 2086 | "args": [ 2087 | { 2088 | "type": "Identifier", 2089 | "name": "c" 2090 | }, 2091 | { 2092 | "type": "Identifier", 2093 | "name": "d" 2094 | } 2095 | ] 2096 | }, 2097 | { 2098 | "type": "Apply", 2099 | "op": "add", 2100 | "args": [ 2101 | { 2102 | "type": "Identifier", 2103 | "name": "e" 2104 | }, 2105 | { 2106 | "type": "Identifier", 2107 | "name": "f" 2108 | } 2109 | ] 2110 | } 2111 | ] 2112 | } 2113 | `; 2114 | 2115 | exports[`Parser.parse relations (n-ary) a + b > c + d > e + f 1`] = ` 2116 | { 2117 | "type": "Apply", 2118 | "op": "gt", 2119 | "args": [ 2120 | { 2121 | "type": "Apply", 2122 | "op": "add", 2123 | "args": [ 2124 | { 2125 | "type": "Identifier", 2126 | "name": "a" 2127 | }, 2128 | { 2129 | "type": "Identifier", 2130 | "name": "b" 2131 | } 2132 | ] 2133 | }, 2134 | { 2135 | "type": "Apply", 2136 | "op": "add", 2137 | "args": [ 2138 | { 2139 | "type": "Identifier", 2140 | "name": "c" 2141 | }, 2142 | { 2143 | "type": "Identifier", 2144 | "name": "d" 2145 | } 2146 | ] 2147 | }, 2148 | { 2149 | "type": "Apply", 2150 | "op": "add", 2151 | "args": [ 2152 | { 2153 | "type": "Identifier", 2154 | "name": "e" 2155 | }, 2156 | { 2157 | "type": "Identifier", 2158 | "name": "f" 2159 | } 2160 | ] 2161 | } 2162 | ] 2163 | } 2164 | `; 2165 | 2166 | exports[`Parser.parse relations (n-ary) a = b = c >= d <= e <= f 1`] = ` 2167 | { 2168 | "type": "Apply", 2169 | "op": "and", 2170 | "args": [ 2171 | { 2172 | "type": "Apply", 2173 | "op": "eq", 2174 | "args": [ 2175 | { 2176 | "type": "Identifier", 2177 | "name": "a" 2178 | }, 2179 | { 2180 | "type": "Identifier", 2181 | "name": "b" 2182 | }, 2183 | { 2184 | "type": "Identifier", 2185 | "name": "c" 2186 | } 2187 | ] 2188 | }, 2189 | { 2190 | "type": "Apply", 2191 | "op": "ge", 2192 | "args": [ 2193 | { 2194 | "type": "Identifier", 2195 | "name": "c" 2196 | }, 2197 | { 2198 | "type": "Identifier", 2199 | "name": "d" 2200 | } 2201 | ] 2202 | }, 2203 | { 2204 | "type": "Apply", 2205 | "op": "le", 2206 | "args": [ 2207 | { 2208 | "type": "Identifier", 2209 | "name": "d" 2210 | }, 2211 | { 2212 | "type": "Identifier", 2213 | "name": "e" 2214 | }, 2215 | { 2216 | "type": "Identifier", 2217 | "name": "f" 2218 | } 2219 | ] 2220 | } 2221 | ] 2222 | } 2223 | `; 2224 | 2225 | exports[`Parser.parse relations (n-ary) a = b = c 1`] = ` 2226 | { 2227 | "type": "Apply", 2228 | "op": "eq", 2229 | "args": [ 2230 | { 2231 | "type": "Identifier", 2232 | "name": "a" 2233 | }, 2234 | { 2235 | "type": "Identifier", 2236 | "name": "b" 2237 | }, 2238 | { 2239 | "type": "Identifier", 2240 | "name": "c" 2241 | } 2242 | ] 2243 | } 2244 | `; 2245 | 2246 | exports[`Parser.parse relations (n-ary) a = b >= c != d < e - f 1`] = ` 2247 | { 2248 | "type": "Apply", 2249 | "op": "and", 2250 | "args": [ 2251 | { 2252 | "type": "Apply", 2253 | "op": "eq", 2254 | "args": [ 2255 | { 2256 | "type": "Identifier", 2257 | "name": "a" 2258 | }, 2259 | { 2260 | "type": "Identifier", 2261 | "name": "b" 2262 | } 2263 | ] 2264 | }, 2265 | { 2266 | "type": "Apply", 2267 | "op": "ge", 2268 | "args": [ 2269 | { 2270 | "type": "Identifier", 2271 | "name": "b" 2272 | }, 2273 | { 2274 | "type": "Identifier", 2275 | "name": "c" 2276 | } 2277 | ] 2278 | }, 2279 | { 2280 | "type": "Apply", 2281 | "op": "ne", 2282 | "args": [ 2283 | { 2284 | "type": "Identifier", 2285 | "name": "c" 2286 | }, 2287 | { 2288 | "type": "Identifier", 2289 | "name": "d" 2290 | } 2291 | ] 2292 | }, 2293 | { 2294 | "type": "Apply", 2295 | "op": "lt", 2296 | "args": [ 2297 | { 2298 | "type": "Identifier", 2299 | "name": "d" 2300 | }, 2301 | { 2302 | "type": "Apply", 2303 | "op": "add", 2304 | "args": [ 2305 | { 2306 | "type": "Identifier", 2307 | "name": "e" 2308 | }, 2309 | { 2310 | "wasMinus": true, 2311 | "type": "Apply", 2312 | "op": "neg", 2313 | "args": [ 2314 | { 2315 | "type": "Identifier", 2316 | "name": "f" 2317 | } 2318 | ] 2319 | } 2320 | ] 2321 | } 2322 | ] 2323 | } 2324 | ] 2325 | } 2326 | `; 2327 | 2328 | exports[`Parser.parse relations (n-ary) a > b > c 1`] = ` 2329 | { 2330 | "type": "Apply", 2331 | "op": "gt", 2332 | "args": [ 2333 | { 2334 | "type": "Identifier", 2335 | "name": "a" 2336 | }, 2337 | { 2338 | "type": "Identifier", 2339 | "name": "b" 2340 | }, 2341 | { 2342 | "type": "Identifier", 2343 | "name": "c" 2344 | } 2345 | ] 2346 | } 2347 | `; 2348 | 2349 | exports[`Parser.parse sequences 1, 1, 2, 3, 5 1`] = ` 2350 | { 2351 | "type": "Sequence", 2352 | "items": [ 2353 | { 2354 | "value": "1", 2355 | "type": "Number" 2356 | }, 2357 | { 2358 | "value": "1", 2359 | "type": "Number" 2360 | }, 2361 | { 2362 | "value": "2", 2363 | "type": "Number" 2364 | }, 2365 | { 2366 | "value": "3", 2367 | "type": "Number" 2368 | }, 2369 | { 2370 | "value": "5", 2371 | "type": "Number" 2372 | } 2373 | ] 2374 | } 2375 | `; 2376 | 2377 | exports[`Parser.parse sequences a, a^3, a^5 1`] = ` 2378 | { 2379 | "type": "Sequence", 2380 | "items": [ 2381 | { 2382 | "type": "Identifier", 2383 | "name": "a" 2384 | }, 2385 | { 2386 | "type": "Apply", 2387 | "op": "pow", 2388 | "args": [ 2389 | { 2390 | "type": "Identifier", 2391 | "name": "a" 2392 | }, 2393 | { 2394 | "value": "3", 2395 | "type": "Number" 2396 | } 2397 | ] 2398 | }, 2399 | { 2400 | "type": "Apply", 2401 | "op": "pow", 2402 | "args": [ 2403 | { 2404 | "type": "Identifier", 2405 | "name": "a" 2406 | }, 2407 | { 2408 | "value": "5", 2409 | "type": "Number" 2410 | } 2411 | ] 2412 | } 2413 | ] 2414 | } 2415 | `; 2416 | 2417 | exports[`Parser.parse sequences r_1, r_2, r_3 1`] = ` 2418 | { 2419 | "type": "Sequence", 2420 | "items": [ 2421 | { 2422 | "type": "Identifier", 2423 | "subscript": { 2424 | "value": "1", 2425 | "type": "Number" 2426 | }, 2427 | "name": "r" 2428 | }, 2429 | { 2430 | "type": "Identifier", 2431 | "subscript": { 2432 | "value": "2", 2433 | "type": "Number" 2434 | }, 2435 | "name": "r" 2436 | }, 2437 | { 2438 | "type": "Identifier", 2439 | "subscript": { 2440 | "value": "3", 2441 | "type": "Number" 2442 | }, 2443 | "name": "r" 2444 | } 2445 | ] 2446 | } 2447 | `; 2448 | 2449 | exports[`Parser.parse sequences x, x + 1, x + 3 1`] = ` 2450 | { 2451 | "type": "Sequence", 2452 | "items": [ 2453 | { 2454 | "type": "Identifier", 2455 | "name": "x" 2456 | }, 2457 | { 2458 | "type": "Apply", 2459 | "op": "add", 2460 | "args": [ 2461 | { 2462 | "type": "Identifier", 2463 | "name": "x" 2464 | }, 2465 | { 2466 | "value": "1", 2467 | "type": "Number" 2468 | } 2469 | ] 2470 | }, 2471 | { 2472 | "type": "Apply", 2473 | "op": "add", 2474 | "args": [ 2475 | { 2476 | "type": "Identifier", 2477 | "name": "x" 2478 | }, 2479 | { 2480 | "value": "3", 2481 | "type": "Number" 2482 | } 2483 | ] 2484 | } 2485 | ] 2486 | } 2487 | `; 2488 | 2489 | exports[`Parser.parse subscripts #a_0 1`] = ` 2490 | { 2491 | "type": "Placeholder", 2492 | "subscript": { 2493 | "value": "0", 2494 | "type": "Number" 2495 | }, 2496 | "name": "a" 2497 | } 2498 | `; 2499 | 2500 | exports[`Parser.parse subscripts #a_a3 1`] = ` 2501 | { 2502 | "type": "Placeholder", 2503 | "subscript": { 2504 | "type": "Identifier", 2505 | "name": "a3" 2506 | }, 2507 | "name": "a" 2508 | } 2509 | `; 2510 | 2511 | exports[`Parser.parse subscripts a_0 1`] = ` 2512 | { 2513 | "type": "Identifier", 2514 | "subscript": { 2515 | "value": "0", 2516 | "type": "Number" 2517 | }, 2518 | "name": "a" 2519 | } 2520 | `; 2521 | 2522 | exports[`Parser.parse subscripts a_0^2 1`] = ` 2523 | { 2524 | "type": "Apply", 2525 | "op": "pow", 2526 | "args": [ 2527 | { 2528 | "type": "Identifier", 2529 | "subscript": { 2530 | "value": "0", 2531 | "type": "Number" 2532 | }, 2533 | "name": "a" 2534 | }, 2535 | { 2536 | "value": "2", 2537 | "type": "Number" 2538 | } 2539 | ] 2540 | } 2541 | `; 2542 | 2543 | exports[`Parser.parse subscripts a_123 1`] = ` 2544 | { 2545 | "type": "Identifier", 2546 | "subscript": { 2547 | "value": "123", 2548 | "type": "Number" 2549 | }, 2550 | "name": "a" 2551 | } 2552 | `; 2553 | 2554 | exports[`Parser.parse subscripts a_n 1`] = ` 2555 | { 2556 | "type": "Identifier", 2557 | "subscript": { 2558 | "type": "Identifier", 2559 | "name": "n" 2560 | }, 2561 | "name": "a" 2562 | } 2563 | `; 2564 | 2565 | exports[`Parser.parse subscripts a_xyz 1`] = ` 2566 | { 2567 | "type": "Identifier", 2568 | "subscript": { 2569 | "type": "Identifier", 2570 | "name": "xyz" 2571 | }, 2572 | "name": "a" 2573 | } 2574 | `; 2575 | 2576 | exports[`Parser.parse systems of equations a = b, b = c, c = d 1`] = ` 2577 | { 2578 | "type": "System", 2579 | "relations": [ 2580 | { 2581 | "type": "Apply", 2582 | "op": "eq", 2583 | "args": [ 2584 | { 2585 | "type": "Identifier", 2586 | "name": "a" 2587 | }, 2588 | { 2589 | "type": "Identifier", 2590 | "name": "b" 2591 | } 2592 | ] 2593 | }, 2594 | { 2595 | "type": "Apply", 2596 | "op": "eq", 2597 | "args": [ 2598 | { 2599 | "type": "Identifier", 2600 | "name": "b" 2601 | }, 2602 | { 2603 | "type": "Identifier", 2604 | "name": "c" 2605 | } 2606 | ] 2607 | }, 2608 | { 2609 | "type": "Apply", 2610 | "op": "eq", 2611 | "args": [ 2612 | { 2613 | "type": "Identifier", 2614 | "name": "c" 2615 | }, 2616 | { 2617 | "type": "Identifier", 2618 | "name": "d" 2619 | } 2620 | ] 2621 | } 2622 | ] 2623 | } 2624 | `; 2625 | 2626 | exports[`Parser.parse systems of equations x + 1 = 2, x^2 + 2x + 1 = 0 1`] = ` 2627 | { 2628 | "type": "System", 2629 | "relations": [ 2630 | { 2631 | "type": "Apply", 2632 | "op": "eq", 2633 | "args": [ 2634 | { 2635 | "type": "Apply", 2636 | "op": "add", 2637 | "args": [ 2638 | { 2639 | "type": "Identifier", 2640 | "name": "x" 2641 | }, 2642 | { 2643 | "value": "1", 2644 | "type": "Number" 2645 | } 2646 | ] 2647 | }, 2648 | { 2649 | "value": "2", 2650 | "type": "Number" 2651 | } 2652 | ] 2653 | }, 2654 | { 2655 | "type": "Apply", 2656 | "op": "eq", 2657 | "args": [ 2658 | { 2659 | "type": "Apply", 2660 | "op": "add", 2661 | "args": [ 2662 | { 2663 | "type": "Apply", 2664 | "op": "pow", 2665 | "args": [ 2666 | { 2667 | "type": "Identifier", 2668 | "name": "x" 2669 | }, 2670 | { 2671 | "value": "2", 2672 | "type": "Number" 2673 | } 2674 | ] 2675 | }, 2676 | { 2677 | "type": "Apply", 2678 | "op": "mul", 2679 | "implicit": true, 2680 | "args": [ 2681 | { 2682 | "value": "2", 2683 | "type": "Number" 2684 | }, 2685 | { 2686 | "type": "Identifier", 2687 | "name": "x" 2688 | } 2689 | ] 2690 | }, 2691 | { 2692 | "value": "1", 2693 | "type": "Number" 2694 | } 2695 | ] 2696 | }, 2697 | { 2698 | "value": "0", 2699 | "type": "Number" 2700 | } 2701 | ] 2702 | } 2703 | ] 2704 | } 2705 | `; 2706 | 2707 | exports[`Parser.parse systems of equations x + 2 = y, 3x - 5 = 2y 1`] = ` 2708 | { 2709 | "type": "System", 2710 | "relations": [ 2711 | { 2712 | "type": "Apply", 2713 | "op": "eq", 2714 | "args": [ 2715 | { 2716 | "type": "Apply", 2717 | "op": "add", 2718 | "args": [ 2719 | { 2720 | "type": "Identifier", 2721 | "name": "x" 2722 | }, 2723 | { 2724 | "value": "2", 2725 | "type": "Number" 2726 | } 2727 | ] 2728 | }, 2729 | { 2730 | "type": "Identifier", 2731 | "name": "y" 2732 | } 2733 | ] 2734 | }, 2735 | { 2736 | "type": "Apply", 2737 | "op": "eq", 2738 | "args": [ 2739 | { 2740 | "type": "Apply", 2741 | "op": "add", 2742 | "args": [ 2743 | { 2744 | "type": "Apply", 2745 | "op": "mul", 2746 | "implicit": true, 2747 | "args": [ 2748 | { 2749 | "value": "3", 2750 | "type": "Number" 2751 | }, 2752 | { 2753 | "type": "Identifier", 2754 | "name": "x" 2755 | } 2756 | ] 2757 | }, 2758 | { 2759 | "wasMinus": true, 2760 | "type": "Apply", 2761 | "op": "neg", 2762 | "args": [ 2763 | { 2764 | "value": "5", 2765 | "type": "Number" 2766 | } 2767 | ] 2768 | } 2769 | ] 2770 | }, 2771 | { 2772 | "type": "Apply", 2773 | "op": "mul", 2774 | "implicit": true, 2775 | "args": [ 2776 | { 2777 | "value": "2", 2778 | "type": "Number" 2779 | }, 2780 | { 2781 | "type": "Identifier", 2782 | "name": "y" 2783 | } 2784 | ] 2785 | } 2786 | ] 2787 | } 2788 | ] 2789 | } 2790 | `; 2791 | 2792 | exports[`Parser.parse unary operators +2 1`] = ` 2793 | { 2794 | "type": "Apply", 2795 | "op": "pos", 2796 | "args": [ 2797 | { 2798 | "value": "2", 2799 | "type": "Number" 2800 | } 2801 | ] 2802 | } 2803 | `; 2804 | 2805 | exports[`Parser.parse unary operators +a 1`] = ` 2806 | { 2807 | "type": "Apply", 2808 | "op": "pos", 2809 | "args": [ 2810 | { 2811 | "type": "Identifier", 2812 | "name": "a" 2813 | } 2814 | ] 2815 | } 2816 | `; 2817 | 2818 | exports[`Parser.parse unary operators --2 1`] = ` 2819 | { 2820 | "type": "Apply", 2821 | "op": "neg", 2822 | "args": [ 2823 | { 2824 | "type": "Apply", 2825 | "op": "neg", 2826 | "args": [ 2827 | { 2828 | "value": "2", 2829 | "type": "Number" 2830 | } 2831 | ] 2832 | } 2833 | ] 2834 | } 2835 | `; 2836 | 2837 | exports[`Parser.parse unary operators --a 1`] = ` 2838 | { 2839 | "type": "Apply", 2840 | "op": "neg", 2841 | "args": [ 2842 | { 2843 | "type": "Apply", 2844 | "op": "neg", 2845 | "args": [ 2846 | { 2847 | "type": "Identifier", 2848 | "name": "a" 2849 | } 2850 | ] 2851 | } 2852 | ] 2853 | } 2854 | `; 2855 | 2856 | exports[`Parser.parse unary operators -2 1`] = ` 2857 | { 2858 | "type": "Apply", 2859 | "op": "neg", 2860 | "args": [ 2861 | { 2862 | "value": "2", 2863 | "type": "Number" 2864 | } 2865 | ] 2866 | } 2867 | `; 2868 | 2869 | exports[`Parser.parse unary operators -a 1`] = ` 2870 | { 2871 | "type": "Apply", 2872 | "op": "neg", 2873 | "args": [ 2874 | { 2875 | "type": "Identifier", 2876 | "name": "a" 2877 | } 2878 | ] 2879 | } 2880 | `; 2881 | -------------------------------------------------------------------------------- /lib/__test__/parser.test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert' 2 | import stringify from 'json-stable-stringify' 3 | 4 | import parse from '../parse' 5 | 6 | const reverseAlphabetical = (a, b) => a.key < b.key ? 1 : -1 7 | 8 | const serializer = { 9 | print(val) { 10 | return stringify(val, {cmp: reverseAlphabetical, space: ' '}) 11 | }, 12 | test() { 13 | return true 14 | }, 15 | } 16 | 17 | expect.addSnapshotSerializer(serializer) // eslint-disable-line 18 | 19 | const suite = (name, cases) => { 20 | describe(name, () => { 21 | cases.forEach((c) => { 22 | it(c, () => { 23 | const ast = parse(c) 24 | expect(ast).toMatchSnapshot() // eslint-disable-line 25 | }) 26 | }) 27 | }) 28 | } 29 | 30 | suite.only = (name, cases) => { 31 | describe.only(name, () => { 32 | cases.forEach((c) => { 33 | it(c, () => { 34 | const ast = parse(c) 35 | expect(ast).toMatchSnapshot() // eslint-disable-line 36 | }) 37 | }) 38 | }) 39 | } 40 | 41 | describe('Parser.parse', () => { 42 | it('should fail with invalid tokens', () => { 43 | assert.throws(() => parse('a ; b')) 44 | assert.throws(() => parse('; a b')) 45 | assert.throws(() => parse('a b ;')) 46 | }) 47 | 48 | suite('addition/subtraction', [ 49 | 'a + b + c', 50 | 'a - b - c', 51 | 'a + -b - c', 52 | 'a - b - -c', 53 | '1 - 2', 54 | '1 - -2', 55 | ]) 56 | 57 | suite('multiplication', [ 58 | 'a b c', 59 | 'a*b c', // mix of explicit and implicit multiplication 60 | 'a b * b * b c', 61 | 'a*b*c', 62 | '(a)(b)(c)', 63 | '(a)b(c)', // a times function b evaluated at c 64 | ]) 65 | 66 | suite('division', [ 67 | 'a/b/c', // (a/b) / c 68 | 'a*b*c/d', // a * b * (c/d) 69 | 'a b c/d', // a * b * (c/d) 70 | 'a/b*c/d', // (a/b) * (c/d) 71 | '(a*b)/(c*d)', // (a*b) / (c*d) 72 | 'a^2/b^2', 73 | '2x/4', 74 | '2(x+1)/4', 75 | ]) 76 | 77 | suite('powers', [ 78 | 'a^b^c', 79 | '-a^-b', 80 | '-1^-2', 81 | 'a^-1.23', 82 | '(-2)^x', 83 | ]) 84 | 85 | suite('functions', [ 86 | // 'f()', 87 | 'f(a,b)', 88 | 'f(a+b)', 89 | 'f(f(a))', 90 | // 'cos^2(x)', 91 | ]) 92 | 93 | suite('nthRoot', [ 94 | 'nthRoot(x)', 95 | 'nthRoot(x, 2)', 96 | 'nthRoot(-27, 3)', 97 | ]) 98 | // TODO: add failing tests for: 99 | // - nthRoot() 100 | // - nthRoot(1, 2, 3) 101 | 102 | suite('abs', [ 103 | '|a - b|', 104 | '||a - b| - |b - c||', 105 | 'abs(-1)', 106 | ]) 107 | 108 | suite('parentheses', [ 109 | 'a * (b + c)', 110 | '(x + y) - (a + b)', 111 | '(a)(b)', 112 | '5 + (3 * 6)', 113 | '5 + ((3 * 6))', 114 | '(1 + 2)', 115 | '((1 + 2))', 116 | '(a * b)', 117 | '((a * b))', 118 | ]) 119 | 120 | suite('unary operators', [ 121 | '-a', 122 | '-2', 123 | '--a', 124 | '--2', 125 | '+a', 126 | '+2', 127 | ]) 128 | 129 | suite('relations (binary)', [ 130 | 'a = b', 131 | 'a > b', 132 | 'a >= b', 133 | 'a < b', 134 | 'a <= b', 135 | 'a != b', 136 | ]) 137 | 138 | suite('relations (n-ary)', [ 139 | 'a = b = c', 140 | 'a + b = c + d = e + f', 141 | 'a > b > c', 142 | 'a + b > c + d > e + f', 143 | 'a != b != c', 144 | 'a + b != c + d != e + f', 145 | 'a = b >= c != d < e - f', 146 | 'a = b = c >= d <= e <= f', 147 | ]) 148 | 149 | suite('systems of equations', [ 150 | 'x + 2 = y, 3x - 5 = 2y', 151 | 'x + 1 = 2, x^2 + 2x + 1 = 0', 152 | 'a = b, b = c, c = d' 153 | ]) 154 | 155 | suite('sequences', [ 156 | '1, 1, 2, 3, 5', 157 | 'x, x + 1, x + 3', 158 | 'a, a^3, a^5', 159 | 'r_1, r_2, r_3', 160 | ]) 161 | 162 | suite('placeholders', [ 163 | '#a', 164 | '#a #b / #c', 165 | '#f(#x)', 166 | '#a #f(#x)', 167 | '#eval(#a + #b)', 168 | ]) 169 | 170 | suite('subscripts', [ 171 | 'a_0', 172 | 'a_123', 173 | 'a_n', 174 | 'a_xyz', 175 | 'a_0^2', 176 | '#a_0', 177 | '#a_a3', 178 | // 'a_-1', 179 | // 'a_(m+n), 180 | // 'a_b_c', 181 | // 'f_n(x)', 182 | ]) 183 | 184 | suite('ellipsis', [ 185 | 'a + ... + z', 186 | '1 * ... * n', 187 | // 'a ... z', // implicit multiplication 188 | '1, 2, ..., n', 189 | '#a_0#x + ... + #a_n#x', 190 | ]) 191 | 192 | suite('factorial', [ 193 | '5!', 194 | 'x!', 195 | '5!^2', 196 | '(5^2)!', 197 | 'x^2!', 198 | '2 * n!', 199 | '(2 * n)!', 200 | ]) 201 | 202 | suite('integrals', [ 203 | 'int x^2 dx', 204 | 'int_0^1 x^2 dx', 205 | 'int_0^1 x^2 2x dx', 206 | 'int_0^1 int_0^1 x^2 y^2 dx dy', 207 | 'int_0^1 int_0^1 x^2 + y^2 dx dy', 208 | ]) 209 | }) 210 | 211 | // TODO: add tests verify different ways of writing the same thing, e.g. 212 | // a*b/c/d/ === a*b/(c*d) 213 | -------------------------------------------------------------------------------- /lib/__test__/print.test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert' 2 | 3 | import parse from '../parse' 4 | import print from '../print' 5 | 6 | describe('print', () => { 7 | describe('wasMinus', () => { 8 | const tests = [ 9 | '1 + -2', 10 | '1 - 2', 11 | '1 - -2', 12 | 'a - b', 13 | 'a + -b', 14 | ] 15 | 16 | tests.forEach(t => 17 | it(t, () => assert.equal(print(parse(t)),t)) 18 | ) 19 | }) 20 | 21 | describe('relations', () => { 22 | const tests = [ 23 | 'a + b = c - d', 24 | 'a + b > c - d', 25 | 'a + b >= c - d', 26 | 'a + b < c - d', 27 | 'a + b <= c - d', 28 | 'a + b != c - d', 29 | ] 30 | 31 | tests.forEach(t => 32 | it(t, () => assert.equal(print(parse(t)),t)) 33 | ) 34 | }) 35 | 36 | describe('fractions', () => { 37 | const tests = [ 38 | ['(x + 1) / 1', '(x + 1) / 1'], 39 | ['2/3 + 5', '2/3 + 5'], 40 | ['2/x + 5', '2/x + 5'], 41 | ['y/x + 5', 'y/x + 5'], 42 | ['y/5 + 5', 'y/5 + 5'], 43 | ['1/2/3', '1/2 / 3'], // (1/2) / 3 44 | ['1*2/3', '1 * 2/3'], // 1 * (2/3) 45 | ['(1*2)/3', '(1 * 2) / 3'], 46 | ['a/(b/c)', 'a / (b/c)'], 47 | ] 48 | 49 | tests.forEach(t => 50 | it(`${t[0]} => ${t[1]}`, () => 51 | assert.equal(print(parse(t[0])),t[1]) 52 | ) 53 | ) 54 | }) 55 | 56 | describe('exponents', () => { 57 | const tests = [ 58 | ['x^2', 'x^2'], 59 | ['x^(x / 2)','x^(x/2)'], 60 | ['x^(y + 1)', 'x^(y + 1)'], 61 | ['x^(x / (x + 2))', 'x^(x / (x + 2))'], 62 | ['x^((x + 1)/(2 * 2))', 'x^((x + 1) / (2 * 2))'], 63 | ['x^(x + x + (x + y))', 'x^(x + x + (x + y))'], 64 | ['(y+1)^((x + 1) + 2)', '(y + 1)^((x + 1) + 2)'], 65 | ['-2^x', '-2^x'], 66 | ['(-2)^x', '(-2)^x'], 67 | ['(-2)^-1', '(-2)^-1'], 68 | ] 69 | 70 | tests.forEach(t => 71 | it(`${t[0]} => ${t[1]}`, () => 72 | assert.equal(print(parse(t[0])),t[1]) 73 | ) 74 | ) 75 | }) 76 | 77 | describe('order of operations', () => { 78 | const tests = [ 79 | '1 * (2 * (3 * 4))', 80 | '((1 * 2) * 3) * 4', 81 | '1 + (2 + (3 + 4))', 82 | '((1 + 2) + 3) + 4', 83 | '2 x y + 3 x y', 84 | '(2 x y) / (3 x y)', 85 | '(x y)^(2 * 3)', 86 | '(x/y)^(2/3)', 87 | '2x * 3 x y', 88 | '(2x) (3 x y)', 89 | ] 90 | 91 | tests.forEach(t => 92 | it(t, () => assert.equal(print(parse(t)),t)) 93 | ) 94 | }) 95 | 96 | describe('subscripts', () => { 97 | const tests = [ 98 | 'a_0', 99 | 'a_123', 100 | 'a_n', 101 | 'a_xyz', 102 | 'a_0^2', 103 | '#a_0', 104 | // 'a_-1', 105 | // 'a_(m+n), 106 | // 'a_b_c', 107 | // 'f_n(x)', 108 | ] 109 | 110 | tests.forEach(t => 111 | it(t, () => assert.equal(print(parse(t)), t)) 112 | ) 113 | }) 114 | 115 | describe('ellipsis', () => { 116 | const tests = [ 117 | 'a + ... + z', 118 | '1 * ... * n', 119 | // 'a ... z', // implicit multiplication 120 | '1, 2, ..., n', 121 | '#a_0 #x + ... + #a_n #x', 122 | ] 123 | 124 | tests.forEach(t => 125 | it(t, () => assert.equal(print(parse(t)), t)) 126 | ) 127 | }) 128 | 129 | describe('functions', () => { 130 | const tests = [ 131 | 'f(x)', 132 | 'gcd(1, 2, 3)', 133 | '#eval(#a_0, ...)', 134 | '#eval(lcm(#a_0, ...))', 135 | ] 136 | 137 | tests.forEach(t => 138 | it(t, () => assert.equal(print(parse(t)), t)) 139 | ) 140 | }) 141 | 142 | describe('parentheses', () => { 143 | const tests = [ 144 | '5 + (3 * 6)', 145 | '5 + ((3 * 6))', 146 | '5 + (((3 * 6)))', 147 | '(1 + 2)', 148 | '((1 + 2))', 149 | '(((1 + 2)))', 150 | '(a * b)', 151 | '((a * b))', 152 | '(((a * b)))', 153 | ] 154 | 155 | tests.forEach(t => 156 | it(t, () => assert.equal(print(parse(t)), t)) 157 | ) 158 | }) 159 | 160 | describe('nthRoot', () => { 161 | const tests = [ 162 | ['nthRoot(x)', 'nthRoot(x, 2)'], 163 | ['nthRoot(x, 3)', 'nthRoot(x, 3)'], 164 | ['nthRoot(8, 3)', 'nthRoot(8, 3)'], 165 | ['nthRoot(x^2, 4)', 'nthRoot(x^2, 4)'], 166 | ] 167 | 168 | tests.forEach(t => 169 | it(t[0], () => assert.equal(print(parse(t[0])), t[1])) 170 | ) 171 | }) 172 | 173 | describe('factorial', () => { 174 | const tests = [ 175 | '5!', 176 | 'n!', 177 | 'x^2!', 178 | '(x + 1)!', 179 | '(5 * 2)!', 180 | '(8/2)!', 181 | '(x^2)!', 182 | ] 183 | 184 | tests.forEach(t => 185 | it(t, () => assert.equal(print(parse(t)), t)) 186 | ) 187 | }) 188 | 189 | describe('abs', () => { 190 | const tests = [ 191 | '|-1|', 192 | '|x|', 193 | '|x + 1|', 194 | '|x| - |y|', 195 | '||x| - |y||', 196 | ] 197 | 198 | tests.forEach(t => 199 | it(t, () => assert.equal(print(parse(t)), t)) 200 | ) 201 | }) 202 | 203 | describe('integral', () => { 204 | const tests = [ 205 | ['int(x^2, x)', 'int(x^2, x)'], 206 | ['int(x^2, x, D)', 'int(x^2, x, D)'], 207 | ['int(x^2, x, a, b)', 'int(x^2, x, a, b)'], 208 | ['int(int(x^2, x, a, b), y, c, d)', 'int(int(x^2, x, a, b), y, c, d)'], 209 | ] 210 | 211 | tests.forEach(t => 212 | it(t[0], () => assert.equal(print(parse(t[0])), t[1])) 213 | ) 214 | }) 215 | 216 | }) 217 | 218 | -------------------------------------------------------------------------------- /lib/__test__/toTex.test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert' 2 | 3 | import parse from '../parse' 4 | import toTex from '../toTex.js' 5 | 6 | describe('toTex', () => { 7 | it('handles wasMinus correctly', () => { 8 | const tests = [ 9 | // arithmetic 10 | ['1 + -2', '1 + -2'], 11 | ['1 - -2', '1 - -2'], 12 | ['a - b', 'a - b'], 13 | ['a + -b', 'a + -b'], 14 | ['1 * 2', '1 \\times 2'], 15 | // implicit multiplication 16 | ['x * 2x', 'x \\times 2 x'], 17 | ['-3', '-3'], 18 | // brackets 19 | //['[2 , 3]', ''] 20 | ] 21 | 22 | tests.forEach(t => assert.equal(toTex(parse(t[0])), t[1])) 23 | }) 24 | 25 | it('handles fractions correctly', () => { 26 | const tests = [ 27 | ['1/2', '\\frac{1}{2}'], 28 | ['1/2/3', '\\frac{\\frac{1}{2}}{3}'], 29 | ['x/2', '\\frac{x}{2}'], 30 | ['(x+2)/(x+3)', '\\frac{\\left(x + 2\\right)}{\\left(x + 3\\right)}'], 31 | ] 32 | 33 | tests.forEach(t => assert.equal(toTex(parse(t[0])), t[1])) 34 | }) 35 | 36 | it('handles equations correctly', () => { 37 | const tests = [ 38 | ['x = 5/2', 'x = \\frac{5}{2}'], 39 | ['x = 3 * (2/x)', 'x = 3 \\times \\frac{2}{x}'], 40 | ['3 + x = 3/x', '\\left(3 + x\\right) = \\frac{3}{x}'], 41 | ] 42 | 43 | tests.forEach(t => assert.equal(toTex(parse(t[0])), t[1])) 44 | }) 45 | 46 | it('relations', () => { 47 | const tests = [ 48 | 'a = b', 49 | 'a > b', 50 | 'a >= b', 51 | 'a < b', 52 | 'a <= b', 53 | 'a != b', 54 | ] 55 | 56 | tests.forEach(test => assert.equal(toTex(parse(test)), test)) 57 | }) 58 | 59 | it('functions', () => { 60 | const tests = [ 61 | // nthRoot 62 | ['nthRoot(x^1, 2)', '\\sqrt{x^1}'], 63 | ['nthRoot(y^-6, -3)', '\\sqrt[-3]{y^-6}'], 64 | ['nthRoot(2, 2)', '\\sqrt{2}'], 65 | ['nthRoot(x^2)', '\\sqrt{x^2}'], 66 | ['nthRoot(x^2 y^2, -2)', '\\sqrt[-2]{x^{2} y^{2}}'], 67 | 68 | // integrals 69 | ['int(x^2, x)', '\\int x^{2} dx'], 70 | ['int(x^2, x, D)', '\\int_D x^{2} dx'], 71 | ['int(x^2, x, a, b)', '\\int_{a}^{b} x^{2} dx'], 72 | ['int(int(x^2 + y^2, x, a, b), y, c, d)', '\\int_{c}^{d} \\int_{a}^{b} \\left(x^{2} + y^{2}\\right) dx dy'] 73 | 74 | ] 75 | 76 | tests.forEach(t => assert.equal(toTex(parse(t[0])), t[1])) 77 | }) 78 | }) 79 | -------------------------------------------------------------------------------- /lib/parse.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Parses a math string to an AST. 3 | * 4 | * Notes: 5 | * - The output AST tries to conform to the math-ast spec, but some aspects may 6 | * be a little off. This will be fixed in future versions. 7 | * - The input syntax covers the parts of the mathjs syntax being used by 8 | * mathsteps 9 | * 10 | * TODO: 11 | * - Better adherence to and more comprehensive coverage of the math-ast spec. 12 | * - Specify what the syntax is, e.g. operator precedence, implicit multiplication, 13 | * etc. 14 | */ 15 | import {build, query} from 'math-nodes' 16 | import {traverse} from 'math-traverse' 17 | 18 | function isIdentifierToken(token) { 19 | return token && /^[a-zA-Z][a-zA-Z0-9]*$/.test(token.value) 20 | } 21 | 22 | function isNumberToken(token) { 23 | return token && /^(\d*\.\d+|\d+\.\d*|\d+)$/.test(token.value) 24 | } 25 | 26 | const tokenPattern = 27 | /\.\.\.|[a-zA-Z][a-zA-Z0-9]*|<=|>=|!=|[\<\>\!\=\(\)\+\-\/\*\^\<\>|\,\#\_]|\d*\.\d+|\d+\.\d*|\d+/ 28 | 29 | const relationTokenMap = { 30 | '=': 'eq', 31 | '<': 'lt', 32 | '<=': 'le', 33 | '>': 'gt', 34 | '>=': 'ge', 35 | '!=': 'ne', 36 | } 37 | 38 | function isIdentifier(node) { 39 | return node.type === 'Identifier' 40 | } 41 | 42 | function isRelation(node) { 43 | return node.type === 'Apply' && Object.values(relationTokenMap).includes(node.op) 44 | } 45 | 46 | function isExpression(node) { 47 | return ['System', 'List', 'Sequence'].indexOf(node.type) === -1 || 48 | isRelation(node) 49 | } 50 | 51 | function matches(token, value) { 52 | return token && token.value === value 53 | } 54 | 55 | class Parser { 56 | consume(expectedValue) { 57 | const token = this.currentToken() 58 | if (expectedValue !== undefined) { 59 | if (!matches(token, expectedValue)) { 60 | throw new Error( 61 | `expected '${expectedValue}' received '${token.value}'`) 62 | } 63 | } 64 | this.i++ 65 | return token 66 | } 67 | 68 | currentToken() { 69 | return this.tokens[this.i] 70 | } 71 | 72 | parse(input) { 73 | this.i = 0 74 | this.tokens = [] 75 | this.integrals = 0 76 | 77 | const regex = new RegExp(tokenPattern, 'g') 78 | 79 | let index = 0 80 | let match 81 | 82 | while ((match = regex.exec(input)) != null) { 83 | const start = match.index 84 | const end = regex.lastIndex 85 | 86 | this.tokens.push({ 87 | value: match[0], 88 | start: start, 89 | end: end, 90 | }) 91 | 92 | if (index !== start) { 93 | const skipped = input.slice(index, start).trim() 94 | if (skipped !== '') { 95 | throw new Error(`'${skipped}' not recognized`) 96 | } 97 | } 98 | 99 | index = end 100 | } 101 | 102 | if (index !== input.length) { 103 | const skipped = input.slice(index, input.length).trim() 104 | if (skipped !== '') { 105 | throw new Error(`'${skipped}' not recognized`) 106 | } 107 | } 108 | 109 | const result = this.list() 110 | 111 | if (this.i < this.tokens.length) { 112 | throw new Error('extra input not recognized') 113 | } 114 | 115 | return result 116 | } 117 | 118 | list() { 119 | const items = [this.relationsOrRelationOrExpression()] 120 | 121 | while (true) { 122 | const token = this.currentToken() 123 | 124 | if (matches(token, ',')) { 125 | this.consume(',') 126 | items.push(this.relationsOrRelationOrExpression()) 127 | } else { 128 | break 129 | } 130 | } 131 | 132 | if (items.length > 1) { 133 | if (items.every((item) => isRelation(item))) { 134 | return { 135 | type: 'System', // of equations 136 | relations: items, 137 | } 138 | } else if (items.every(isExpression)) { 139 | return { 140 | type: 'Sequence', 141 | items, 142 | } 143 | } else { 144 | return { 145 | type: 'List', 146 | items, 147 | } 148 | } 149 | } else { 150 | return items[0] 151 | } 152 | } 153 | 154 | relationsOrRelationOrExpression() { 155 | const relations = [] 156 | const args = [] 157 | 158 | let left 159 | let right 160 | 161 | left = this.expression() 162 | 163 | while (true) { 164 | const token = this.currentToken() 165 | 166 | if (token && token.value in relationTokenMap) { 167 | this.consume() 168 | right = this.expression() 169 | const rel = relationTokenMap[token.value] 170 | relations.push(build.applyNode(rel, [left, right])) 171 | args.push(left) 172 | left = right 173 | } else { 174 | break 175 | } 176 | } 177 | args.push(right) 178 | 179 | if (relations.length > 1) { 180 | relations.reverse() 181 | 182 | let output = { 183 | type: 'Apply', 184 | op: 'and', 185 | args: [] 186 | } 187 | 188 | let current = { 189 | type: 'Apply', 190 | op: relations[0].op, 191 | args: [] 192 | } 193 | 194 | relations.forEach(function (item) { 195 | if (item.op !== current.op) { 196 | current.args.unshift(item.args[1]) 197 | 198 | output.args.unshift(current) 199 | 200 | current = { 201 | type: 'Apply', 202 | op: item.op, 203 | args: [item.args[1]] 204 | } 205 | } 206 | else { 207 | current.args.unshift(item.args[1]) 208 | } 209 | }) 210 | 211 | current.args.unshift(relations[relations.length - 1].args[0]) 212 | output.args.unshift(current) 213 | 214 | if (output.args.length === 1) { 215 | return output.args[0] 216 | } 217 | 218 | return output 219 | } else if (relations.length > 0) { 220 | return relations[0] 221 | } else { 222 | return left 223 | } 224 | } 225 | 226 | expression() { 227 | const args = [] 228 | 229 | args.push(this.explicitMul()) 230 | 231 | while (true) { 232 | const token = this.currentToken() 233 | 234 | if (matches(token, '-')) { 235 | this.consume('-') 236 | args.push( 237 | build.applyNode('neg', [this.explicitMul()], {wasMinus: true}) 238 | ) 239 | } else if (matches(token, '+')) { 240 | this.consume('+') 241 | args.push(this.explicitMul()) 242 | } else { 243 | break 244 | } 245 | } 246 | 247 | if (args.length > 1) { 248 | return build.applyNode('add', args.map(term => 249 | term.addParens ? build.parensNode(term) : term)) 250 | } else { 251 | if (args[0].addParens) { 252 | return build.parensNode(args[0]) 253 | } else { 254 | return args[0] 255 | } 256 | } 257 | } 258 | 259 | explicitMul() { 260 | const factors = [] 261 | 262 | factors.push(this.implicitMul()) 263 | 264 | while (true) { 265 | if (matches(this.currentToken(), '*')) { 266 | this.consume('*') 267 | factors.push(this.implicitMul()) 268 | } else { 269 | break 270 | } 271 | } 272 | 273 | return factors.length > 1 274 | ? build.applyNode('mul', factors) 275 | : factors[0] 276 | } 277 | 278 | /** 279 | * Parse the following forms of implicit multiplication: 280 | * - a b c 281 | * - (a)(b)(c) 282 | * 283 | * Note: (a)b(c) is actually: 'a' times function 'b' evaluated at 'c' 284 | * 285 | * If the multiplication was detected, a single parsed factor is returned. 286 | */ 287 | implicitMul() { 288 | let factors = [] 289 | 290 | factors.push(this.division()) 291 | 292 | while (true) { 293 | let token = this.currentToken() 294 | 295 | if (matches(token, '(') || matches(token, '#') || isIdentifierToken(token) || isNumberToken(token)) { 296 | const factor = this.division() 297 | if (factor) { 298 | factors.push(factor) 299 | } else { 300 | break 301 | } 302 | } else { 303 | break 304 | } 305 | 306 | if (this.i > this.tokens.length) { 307 | break 308 | } 309 | } 310 | 311 | return factors.length > 1 312 | ? build.applyNode('mul', factors, {implicit: true}) 313 | : factors[0] 314 | } 315 | 316 | division() { 317 | let numerator 318 | let denominator 319 | let frac 320 | 321 | numerator = this.factor() 322 | 323 | while (true) { 324 | const token = this.currentToken() 325 | 326 | if (matches(token, '/')) { 327 | this.consume('/') 328 | denominator = this.factor() 329 | if (frac) { 330 | frac = build.applyNode('div', [frac, denominator]) 331 | } else { 332 | frac = build.applyNode('div', [numerator, denominator]) 333 | } 334 | } else { 335 | break 336 | } 337 | } 338 | 339 | return frac || numerator 340 | } 341 | 342 | /** 343 | * Parse any of the following: 344 | * - unary operations, e.g. +, - 345 | * - numbers 346 | * - identifiers 347 | * - parenthesis 348 | * - absolute value function, e.g. |x| 349 | * - exponents, e.g. x^2 350 | */ 351 | factor() { 352 | let token = this.currentToken() 353 | 354 | if (matches(token, '...')) { 355 | this.consume('...') 356 | return { 357 | type: 'Ellipsis', 358 | } 359 | } 360 | 361 | let signs = [] 362 | 363 | // handle multiple unary operators 364 | while (matches(token, '+') || matches(token, '-')) { 365 | signs.push(token) 366 | this.consume(token.value) 367 | token = this.currentToken() 368 | } 369 | 370 | let base, exp 371 | let addParens = false 372 | 373 | if (matches(token, '#') || isIdentifierToken(token)) { 374 | const node = this.identifierOrPlaceholder() 375 | 376 | if (this.integrals > 0 && isIdentifier(node) && /d[a-z]+/.test(node.name)) { 377 | // backup 378 | this.integrals-- 379 | return 380 | } 381 | 382 | if (matches(this.currentToken(), '(')) { 383 | this.consume('(') 384 | const args = this.argumentList() 385 | token = this.consume(')') 386 | if (node.name === 'nthRoot') { 387 | if (args.length < 1 || args.length > 2) { 388 | throw new Error('nthRoot takes 1 or 2 args') 389 | } else { 390 | base = build.nthRoot(...args) 391 | } 392 | } else if (node.name === 'int') { 393 | if (args.length >= 2 && args.length <= 4) { 394 | base = build.apply('int', args) 395 | } else { 396 | throw new Error('integral takes between 2 and 4 args') 397 | } 398 | } else { 399 | if (query.isIdentifier(node) && node.name === 'abs') { 400 | base = build.apply('abs', args) 401 | } else { 402 | base = build.apply(node, args) 403 | } 404 | } 405 | } else { 406 | // TODO(kevinb) valid the constraint type against the node 407 | // e.g. if it's a 'Number' then it can't have a subscript 408 | base = node 409 | } 410 | } else if (isNumberToken(token)) { 411 | this.consume(token.value) 412 | base = build.numberNode(token.value) 413 | } else if (matches(token, '(')) { 414 | this.consume('(') 415 | base = this.expression() 416 | token = this.consume(')') 417 | addParens = true 418 | if (base.type === 'Number' || isIdentifier(base)) { 419 | base = build.parensNode(base) 420 | addParens = false 421 | } 422 | } else if (matches(token, '|')) { 423 | this.consume('|') 424 | base = this.expression() 425 | token = this.consume('|') 426 | 427 | base = build.applyNode('abs', [base]) 428 | } 429 | 430 | if (matches(this.currentToken(), '!')) { 431 | this.consume('!') 432 | // print will add parentheses back in if a 'fact' node wraps the 433 | // expression. 434 | addParens = false 435 | base = build.applyNode('fact', [base]) 436 | } 437 | 438 | let factor = base 439 | 440 | // TODO handle exponents separately 441 | if (matches(this.currentToken(), '^')) { 442 | this.consume('^') 443 | exp = this.factor() 444 | factor = build.applyNode('pow', [base, exp]) 445 | addParens = false 446 | } 447 | 448 | // Reverse the signs so that we process them from the sign nearest 449 | // to the factor to the furthest. 450 | signs.reverse() 451 | 452 | signs.forEach((sign) => { 453 | if (sign.value === '+') { 454 | factor = build.applyNode('pos', [factor]) 455 | } else { 456 | factor = build.applyNode('neg', [factor]) 457 | } 458 | addParens = false 459 | }) 460 | 461 | if (addParens) { 462 | factor.addParens = addParens 463 | } 464 | 465 | if (query.isPow(factor)) { 466 | const [base, exponent] = factor.args 467 | if (query.isIdentifier(base) && base.name === 'int') { 468 | this.integrals++ 469 | const body = this.expression() 470 | 471 | // backup to get the token we ignore 472 | this.i-- 473 | token = this.currentToken() 474 | this.consume(token.value) 475 | 476 | const result = { 477 | type: 'Apply', 478 | op: 'int', 479 | args: [body, build.identifier(token.value)], 480 | limits: [base.subscript, exponent], 481 | } 482 | 483 | return result 484 | } 485 | } else if (query.isIdentifier(factor) && factor.name === 'int') { 486 | this.integrals++ 487 | const body = this.expression() 488 | 489 | // backup to get the token we ignore 490 | this.i-- 491 | token = this.currentToken() 492 | this.consume(token.value) 493 | 494 | const result = { 495 | type: 'Apply', 496 | op: 'int', 497 | args: [body, build.identifier(token.value)], 498 | } 499 | 500 | return result 501 | } 502 | 503 | return factor 504 | } 505 | 506 | identifierOrPlaceholder() { 507 | let token = this.currentToken() 508 | 509 | let isPlaceholder = false 510 | if (matches(token, '#')) { 511 | isPlaceholder = true 512 | this.consume('#') 513 | token = this.currentToken() 514 | } 515 | 516 | if (!isIdentifierToken(token)) { 517 | throw new Error('\'#\' must be followed by an identifier') 518 | } 519 | 520 | const result = this.identifier() 521 | 522 | if (isPlaceholder) { 523 | result.type = 'Placeholder' 524 | } 525 | 526 | return result 527 | } 528 | 529 | identifier() { 530 | let token = this.currentToken() 531 | 532 | const name = token.value 533 | const result = build.identifierNode(name) 534 | this.consume(name) 535 | 536 | token = this.currentToken() 537 | 538 | // This only handles very simple subscripts, e.g. a_0, a_n 539 | // It doesn't handle: a_-1, a_(m+n), etc. 540 | // The precedence of subscripts is very high: a_0^2 => (a_0)^2 541 | if (matches(token, '_')) { 542 | this.consume('_') 543 | 544 | token = this.currentToken() 545 | 546 | if (isNumberToken(token)) { 547 | result.subscript = build.numberNode(token.value) 548 | this.consume(token.value) 549 | } else if (isIdentifierToken(token)) { 550 | result.subscript = build.identifierNode(token.value) 551 | this.consume(token.value) 552 | } else { 553 | throw new Error(`Can't handle '${token.value}' as a subscript`) 554 | } 555 | } 556 | 557 | return result 558 | } 559 | 560 | argumentList() { 561 | const args = [this.expression()] 562 | while (true) { 563 | const token = this.currentToken() 564 | if (!matches(token, ',')) { 565 | break 566 | } 567 | this.consume(',') 568 | args.push(this.expression()) 569 | } 570 | return args 571 | } 572 | } 573 | 574 | const parser = new Parser() 575 | 576 | export default function parse(math) { 577 | const ast = parser.parse(math) 578 | traverse(ast, { 579 | leave(node) { 580 | if (node.hasOwnProperty('addParens')) { 581 | delete node.addParens 582 | } 583 | }, 584 | }) 585 | return ast 586 | } 587 | -------------------------------------------------------------------------------- /lib/print.js: -------------------------------------------------------------------------------- 1 | /** 2 | * print - return a string representation of the nodes. 3 | */ 4 | import {query} from 'math-nodes' 5 | 6 | const relationIdentifierMap = { 7 | 'eq': '=', 8 | 'lt': '<', 9 | 'le': '<=', 10 | 'gt': '>', 11 | 'ge': '>=', 12 | 'ne': '!=', 13 | } 14 | 15 | const printApply = (node, parent) => { 16 | const {op, args} = node 17 | 18 | if (op === 'add') { 19 | let result = print(args[0], node) 20 | for (let i = 1; i < args.length; i++) { 21 | const arg = args[i] 22 | if (query.isNeg(arg) && arg.wasMinus) { 23 | result += ` - ${print(arg.args[0], node)}` 24 | } else { 25 | result += ` + ${print(arg, node)}` 26 | } 27 | } 28 | return parent && !query.isRel(parent) && parent.type !== 'Parentheses' 29 | ? `(${result})` 30 | : result 31 | } else if (op === 'mul') { 32 | let result 33 | // print coefficients with no spaces when possible - e.g. 2x not 2 x 34 | // if there are more than 3 args, we can't do 2xy because if that was 35 | // reparsed it'd treat xy as one symbol 36 | if (node.implicit && node.args.length === 2 && 37 | query.isNumber(node.args[0]) && query.isIdentifier(node.args[1])) { 38 | result = args.map(arg => print(arg, node)).join('') 39 | } else if (node.implicit) { 40 | result = args.map(arg => print(arg, node)).join(' ') 41 | } else { 42 | result = args.map(arg => print(arg, node)).join(' * ') 43 | } 44 | 45 | if (query.isMul(parent)) { 46 | if (node.implicit && !parent.implicit) { 47 | return result 48 | } else { 49 | return `(${result})` 50 | } 51 | } else if (query.isPow(parent) || query.isDiv(parent)) { 52 | return `(${result})` 53 | } else { 54 | return result 55 | } 56 | } else if (op === 'div') { 57 | let result = '' 58 | // this lets us print things like 2/3 and x/5 instead of 2 / 3 and x / 5 59 | // (but the spaces are helpful for reading more complicated fractions) 60 | if ((query.isIdentifier(args[0]) || query.isNumber(args[0])) 61 | && (query.isIdentifier(args[1]) || query.isNumber(args[1]))) { 62 | result = `${print(args[0])}/${print(args[1])}` 63 | } else { 64 | result += print(args[0], node) 65 | result += ' / ' 66 | if (query.isDiv(args[1])) { 67 | result += `(${print(args[1], node)})` 68 | } else { 69 | result += print(args[1], node) 70 | } 71 | } 72 | return query.isPow(parent) 73 | ? `(${result})` 74 | : result 75 | } else if (op === 'pow') { 76 | const [base, exp] = node.args 77 | return query.isNeg(base) 78 | ? `(${print(base, node)})^${print(exp, node)}` 79 | : `${print(base, node)}^${print(exp, node)}` 80 | } else if (op === 'neg') { 81 | return `-${print(args[0], node)}` 82 | } else if (op === 'pos') { 83 | return `+${print(args[0], node)}` 84 | } else if (op === 'pn') { 85 | throw new Error('we don\'t handle \'pn\' operations yet') 86 | } else if (op === 'np') { 87 | throw new Error('we don\'t handle \'np\' operations yet') 88 | } else if (op === 'fact') { 89 | if (args[0].op === 'pow' || args[0].op === 'mul' || args[0].op === 'div') { 90 | return `(${print(args[0], node)})!` 91 | } else { 92 | return `${print(args[0], node)}!` 93 | } 94 | } else if (op === 'nthRoot') { 95 | return `nthRoot(${args.map(arg => print(arg, node)).join(', ')})` 96 | } else if (op === 'int') { 97 | return `int(${args.map(arg => print(arg, node)).join(', ')})` 98 | } else if (op === 'abs') { 99 | return `|${print(args[0])}|` 100 | } else if (op in relationIdentifierMap) { 101 | const symbol = relationIdentifierMap[op] 102 | return args.map(arg => print(arg, node)).join(` ${symbol} `) 103 | } else { 104 | return `${print(op)}(${args.map(arg => print(arg, node)).join(', ')})` 105 | } 106 | } 107 | 108 | export default function print(node, parent = null) { 109 | switch (node.type) { 110 | // regular non-leaf nodes 111 | case 'Apply': 112 | return printApply(node, parent) 113 | 114 | // irregular non-leaf nodes 115 | case 'Parentheses': 116 | return `(${print(node.body, node)})` 117 | 118 | case 'Sequence': 119 | return node.items.map(print).join(', ') 120 | 121 | // leaf nodes 122 | case 'Identifier': 123 | if (node.subscript) { 124 | return `${node.name}_${print(node.subscript)}` 125 | } else { 126 | return node.name 127 | } 128 | 129 | case 'Placeholder': 130 | if (node.subscript) { 131 | return `#${node.name}_${print(node.subscript)}` 132 | } else { 133 | return `#${node.name}` 134 | } 135 | 136 | case 'Number': 137 | return node.value 138 | 139 | case 'Ellipsis': 140 | return '...' 141 | 142 | default: 143 | console.log(node) // eslint-disable-line no-console 144 | throw new Error('unrecognized node') 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /lib/toTex.js: -------------------------------------------------------------------------------- 1 | /** 2 | * toTex - return a string representation of the nodes in LaTeX 3 | */ 4 | 5 | import {query} from 'math-nodes' 6 | 7 | const relationIdentifierMap = { 8 | 'eq': '=', 9 | 'lt': '<', 10 | 'le': '<=', 11 | 'gt': '>', 12 | 'ge': '>=', 13 | 'ne': '!=', 14 | } 15 | 16 | const applyToTex = (node, parent) => { 17 | const {op, args} = node 18 | 19 | if (op === 'add') { 20 | //e.g a + (-a) => a - a 21 | let result = toTex(node.args[0], node) 22 | for (let i = 1; i < node.args.length; i++){ 23 | const arg = node.args[i] 24 | if (query.isNeg(arg) && arg.wasMinus){ 25 | result += ` - ${toTex(arg.args[0], node)}` 26 | } else { 27 | result += ` + ${toTex(arg, node)}` 28 | } 29 | } 30 | return parent ? `\\left(${result}\\right)` : result 31 | } else if (op === 'mul') { 32 | if (node.implicit) { 33 | //e.g 2 x 34 | return node.args.map(arg => toTex(arg, node)).join(' ') 35 | } else { 36 | //e.g 2 * x 37 | return node.args.map(arg => toTex(arg, node)).join(' \\times ') 38 | } 39 | } else if (op === 'div') { 40 | let result = '' 41 | result += '\\frac' 42 | //add parentheses when numerator or denominator has multiple terms 43 | //e.g latex fractions: \frac{a}{b} => a/b 44 | result += `{${toTex(node.args[0], node)}}` 45 | result += `{${toTex(node.args[1], node)}}` 46 | return result 47 | } else if (op === 'pow') { 48 | return `${toTex(node.args[0], node)}^{${toTex(node.args[1], node)}}` 49 | } else if (op === 'neg') { 50 | return `-${toTex(args[0], node)}` 51 | } else if (op === 'pos') { 52 | return `+${toTex(args[0], node)}` 53 | } else if (op === 'pn') { 54 | throw new Error('we don\'t handle \'pn\' operations yet') 55 | } else if (op === 'np') { 56 | throw new Error('we don\'t handle \'np\' operations yet') 57 | } else if (op === 'fact') { 58 | throw new Error('we dont handle fact operations yet') 59 | } else if (op === 'nthRoot') { 60 | const [radicand, index] = node.args 61 | 62 | let base, exponent 63 | let result 64 | 65 | if (query.isPow(radicand)) { 66 | [base, exponent] = radicand.args 67 | result = index == '' || query.getValue(index) == 2 68 | ? `\\sqrt{${toTex(base, node)}^${toTex(exponent, node)}}` 69 | : `\\sqrt[${toTex(index, node)}]{${toTex(base, node)}^${toTex(exponent, node)}}` 70 | } else { 71 | base = radicand 72 | result = index == '' || query.getValue(index) == 2 73 | ? `\\sqrt{${toTex(base, node)}}` 74 | : `\\sqrt[${toTex(index, node)}]{${toTex(base, node)}}` 75 | } 76 | return result 77 | } else if (op === 'int') { 78 | let result 79 | if (node.args.length == 2) { 80 | const [input, variable] = node.args 81 | result = `\\int ${toTex(input, node)} d${toTex(variable, node)}` 82 | } else if (node.args.length == 3) { 83 | const [input, variable, domain] = node.args 84 | result = `\\int_${toTex(domain, node)} ${toTex(input, node)} d${toTex(variable, node)}` 85 | } else if (node.args.length == 4) { 86 | const [input, variable, upper, lower] = node.args 87 | result = `\\int_{${toTex(upper, node)}}^{${toTex(lower, node)}} ${toTex(input, node)} d${toTex(variable, node)}` 88 | } 89 | return result 90 | } else if (op in relationIdentifierMap) { 91 | const symbol = relationIdentifierMap[op] 92 | return args.map(arg => toTex(arg, node)).join(` ${symbol} `) 93 | } else { 94 | return `${op}(${args.map(arg => toTex(arg, node)).join(', ')})` 95 | } 96 | } 97 | 98 | export default function toTex(node, parent = null){ 99 | switch(node.type){ 100 | // regular non-leaf nodes 101 | case 'Apply': 102 | return applyToTex(node, parent) 103 | 104 | // irregular node-leaf nodes 105 | case 'Parentheses': 106 | return `\left(${toTex(node.body, node)}\right)` 107 | 108 | //leaf nodes 109 | case 'Identifier': 110 | return node.name 111 | case 'Number': 112 | return node.value 113 | 114 | default: 115 | console.log(node) // eslint-disable-line no-console 116 | throw new Error('unrecognized node') 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "math-parser", 3 | "version": "0.13.0", 4 | "description": "Parse math strings to an AST suitable for symbolic manipulation.", 5 | "main": "dist/math-parser.js", 6 | "scripts": { 7 | "prepublish": "webpack", 8 | "watch": "webpack -w", 9 | "test": "jest", 10 | "lint": "eslint . --cache" 11 | }, 12 | "pre-commit": [ 13 | "lint" 14 | ], 15 | "jest": { 16 | "setupTestFrameworkScriptFile": "/node_modules/babel-polyfill/dist/polyfill.js", 17 | "transform": { 18 | "^.+\\.jsx?$": "babel-jest" 19 | } 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/kevinbarabash/math-parser.git" 24 | }, 25 | "author": "Kevin Barabash ", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/kevinbarabash/math-parser/issues" 29 | }, 30 | "homepage": "https://github.com/kevinbarabash/math-parser#readme", 31 | "devDependencies": { 32 | "babel-core": "^6.22.1", 33 | "babel-eslint": "^7.1.1", 34 | "babel-loader": "^6.2.10", 35 | "babel-plugin-transform-flow-strip-types": "^6.22.0", 36 | "babel-plugin-transform-object-rest-spread": "^6.22.0", 37 | "babel-polyfill": "^6.23.0", 38 | "babel-preset-es2015": "^6.22.0", 39 | "eslint": "^3.15.0", 40 | "eslint-plugin-flowtype": "^2.30.0", 41 | "flow-bin": "^0.38.0", 42 | "jest": "^20.0.3", 43 | "json-stable-stringify": "^1.0.1", 44 | "pre-commit": "^1.2.2", 45 | "webpack": "^2.2.1" 46 | }, 47 | "dependencies": { 48 | "math-nodes": "^0.1.7", 49 | "math-traverse": "^0.2.2" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | entry: './index.js', 5 | output: { 6 | path: path.join(__dirname, 'dist'), 7 | filename: 'math-parser.js', 8 | libraryTarget: 'commonjs2' 9 | }, 10 | module: { 11 | loaders: [ 12 | { 13 | test: /\.js$/, 14 | exclude: /node_modules/, 15 | loader: 'babel-loader', 16 | } 17 | ] 18 | } 19 | } 20 | --------------------------------------------------------------------------------