@khanacademy/kas 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,15 +1,101 @@
1
- import { addLibraryVersionToPerseusDebug } from '@khanacademy/perseus-utils';
2
- import _extends from '@babel/runtime/helpers/extends';
3
- import _ from 'underscore';
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var perseusUtils = require('@khanacademy/perseus-utils');
6
+ var _ = require('underscore');
7
+
8
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
9
+
10
+ var ___default = /*#__PURE__*/_interopDefaultCompat(_);
11
+
12
+ // This file is processed by a Rollup plugin (replace) to inject the production
13
+ // version number during the release build.
14
+ // In dev, you'll never see the version number.
4
15
 
5
16
  const libName = "@khanacademy/kas";
6
- const libVersion = "1.0.0";
7
- addLibraryVersionToPerseusDebug(libName, libVersion);
17
+ const libVersion = "2.0.0";
18
+ perseusUtils.addLibraryVersionToPerseusDebug(libName, libVersion);
19
+
20
+ // this is a @generated file
21
+
22
+ /* parser generated by jison 0.4.15 */
23
+ /*
24
+ Returns a Parser object of the following structure:
25
+
26
+ Parser: {
27
+ yy: {}
28
+ }
29
+
30
+ Parser.prototype: {
31
+ yy: {},
32
+ trace: function(),
33
+ symbols_: {associative list: name ==> number},
34
+ terminals_: {associative list: number ==> name},
35
+ productions_: [...],
36
+ performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$),
37
+ table: [...],
38
+ defaultActions: {...},
39
+ parseError: function(str, hash),
40
+ parse: function(input),
41
+
42
+ lexer: {
43
+ EOF: 1,
44
+ parseError: function(str, hash),
45
+ setInput: function(input),
46
+ input: function(),
47
+ unput: function(str),
48
+ more: function(),
49
+ less: function(n),
50
+ pastInput: function(),
51
+ upcomingInput: function(),
52
+ showPosition: function(),
53
+ test_match: function(regex_match_array, rule_index),
54
+ next: function(),
55
+ lex: function(),
56
+ begin: function(condition),
57
+ popState: function(),
58
+ _currentRules: function(),
59
+ topState: function(),
60
+ pushState: function(condition),
61
+
62
+ options: {
63
+ ranges: boolean (optional: true ==> token location info will include a .range[] member)
64
+ flex: boolean (optional: true ==> flex-like lexing behaviour where the rules are tested exhaustively to find the longest match)
65
+ backtrack_lexer: boolean (optional: true ==> lexer regexes are tested in order and for each matching regex the action code is invoked; the lexer terminates the scan when a token is returned by the action code)
66
+ },
67
+
68
+ performAction: function(yy, yy_, $avoiding_name_collisions, YY_START),
69
+ rules: [...],
70
+ conditions: {associative list: name ==> set},
71
+ }
72
+ }
73
+
74
+
75
+ token location info (@$, _$, etc.): {
76
+ first_line: n,
77
+ last_line: n,
78
+ first_column: n,
79
+ last_column: n,
80
+ range: [start_number, end_number] (where the numbers are indexes into the input string, regular zero-based)
81
+ }
82
+
8
83
 
84
+ the parseError function receives a 'hash' object with these members for lexer and parser errors: {
85
+ text: (matched text)
86
+ token: (the produced terminal token, if any)
87
+ line: (yylineno)
88
+ }
89
+ while parser (grammar) errors will also provide these members, i.e. parser errors deliver a superset of attributes: {
90
+ loc: (yylloc)
91
+ expected: (string describing the set of expected tokens)
92
+ recoverable: (boolean: TRUE when the parser has a error recovery rule available for this particular error)
93
+ }
94
+ */
9
95
  var parser$1 = function () {
10
- var o = function o(k, v, _o, l) {
11
- for (_o = _o || {}, l = k.length; l--; _o[k[l]] = v);
12
- return _o;
96
+ var o = function (k, v, o, l) {
97
+ for (o = o || {}, l = k.length; l--; o[k[l]] = v);
98
+ return o;
13
99
  },
14
100
  $V0 = [1, 11],
15
101
  $V1 = [1, 9],
@@ -55,7 +141,9 @@ var parser$1 = function () {
55
141
  20: "NEG"
56
142
  },
57
143
  productions_: [0, [3, 3], [3, 2], [4, 3], [4, 1], [5, 3], [5, 1], [10, 3], [10, 2], [10, 1], [12, 3], [12, 1], [14, 1], [7, 1], [7, 1], [16, 1], [9, 2], [9, 1]],
58
- performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$) {
144
+ performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) {
145
+ /* this == yyval */
146
+
59
147
  var $0 = $$.length - 1;
60
148
  switch (yystate) {
61
149
  case 1:
@@ -240,6 +328,7 @@ var parser$1 = function () {
240
328
  } else {
241
329
  this.parseError = Object.getPrototypeOf(this).parseError;
242
330
  }
331
+ //_token_stack:
243
332
  function lex() {
244
333
  var token;
245
334
  token = lexer.lex() || EOF;
@@ -339,6 +428,7 @@ var parser$1 = function () {
339
428
  return true;
340
429
  }
341
430
  };
431
+ /* generated by jison-lex 0.3.4 */
342
432
  var lexer = function () {
343
433
  var lexer = {
344
434
  EOF: 1,
@@ -349,6 +439,7 @@ var parser$1 = function () {
349
439
  throw new Error(str);
350
440
  }
351
441
  },
442
+ // resets the lexer, sets new input
352
443
  setInput: function (input, yy) {
353
444
  this.yy = yy || this.yy || {};
354
445
  this._input = input;
@@ -368,6 +459,7 @@ var parser$1 = function () {
368
459
  this.offset = 0;
369
460
  return this;
370
461
  },
462
+ // consumes and returns one char from the input
371
463
  input: function () {
372
464
  var ch = this._input[0];
373
465
  this.yytext += ch;
@@ -388,11 +480,13 @@ var parser$1 = function () {
388
480
  this._input = this._input.slice(1);
389
481
  return ch;
390
482
  },
483
+ // unshifts one char (or a string) into the input
391
484
  unput: function (ch) {
392
485
  var len = ch.length;
393
486
  var lines = ch.split(/(?:\r\n?|\n)/g);
394
487
  this._input = ch + this._input;
395
488
  this.yytext = this.yytext.substr(0, this.yytext.length - len);
489
+ //this.yyleng -= len;
396
490
  this.offset -= len;
397
491
  var oldLines = this.match.split(/(?:\r\n?|\n)/g);
398
492
  this.match = this.match.substr(0, this.match.length - 1);
@@ -413,10 +507,12 @@ var parser$1 = function () {
413
507
  this.yyleng = this.yytext.length;
414
508
  return this;
415
509
  },
510
+ // When called from action, caches matched text and appends it on next action
416
511
  more: function () {
417
512
  this._more = true;
418
513
  return this;
419
514
  },
515
+ // When called from action, signals the lexer that this rule fails to match the input, so the next matching rule (regex) should be tested instead.
420
516
  reject: function () {
421
517
  if (this.options.backtrack_lexer) {
422
518
  this._backtrack = true;
@@ -429,13 +525,16 @@ var parser$1 = function () {
429
525
  }
430
526
  return this;
431
527
  },
528
+ // retain first n characters of the match
432
529
  less: function (n) {
433
530
  this.unput(this.match.slice(n));
434
531
  },
532
+ // displays already matched input, i.e. for error messages
435
533
  pastInput: function () {
436
534
  var past = this.matched.substr(0, this.matched.length - this.match.length);
437
535
  return (past.length > 20 ? '...' : '') + past.substr(-20).replace(/\n/g, "");
438
536
  },
537
+ // displays upcoming input, i.e. for error messages
439
538
  upcomingInput: function () {
440
539
  var next = this.match;
441
540
  if (next.length < 20) {
@@ -443,14 +542,17 @@ var parser$1 = function () {
443
542
  }
444
543
  return (next.substr(0, 20) + (next.length > 20 ? '...' : '')).replace(/\n/g, "");
445
544
  },
545
+ // displays the character position where the lexing error occurred, i.e. for error messages
446
546
  showPosition: function () {
447
547
  var pre = this.pastInput();
448
548
  var c = new Array(pre.length + 1).join("-");
449
549
  return pre + this.upcomingInput() + "\n" + c + "^";
450
550
  },
551
+ // test the lexed token: return FALSE when not a match, otherwise return token
451
552
  test_match: function (match, indexed_rule) {
452
553
  var token, lines, backup;
453
554
  if (this.options.backtrack_lexer) {
555
+ // save context
454
556
  backup = {
455
557
  yylineno: this.yylineno,
456
558
  yylloc: {
@@ -503,13 +605,15 @@ var parser$1 = function () {
503
605
  if (token) {
504
606
  return token;
505
607
  } else if (this._backtrack) {
608
+ // recover context
506
609
  for (var k in backup) {
507
610
  this[k] = backup[k];
508
611
  }
509
- return false;
612
+ return false; // rule action called reject() implying the next rule should be tested instead.
510
613
  }
511
614
  return false;
512
615
  },
616
+ // return next match in input
513
617
  next: function () {
514
618
  if (this.done) {
515
619
  return this.EOF;
@@ -534,8 +638,9 @@ var parser$1 = function () {
534
638
  return token;
535
639
  } else if (this._backtrack) {
536
640
  match = false;
537
- continue;
641
+ continue; // rule action called reject() implying a rule MISmatch.
538
642
  } else {
643
+ // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
539
644
  return false;
540
645
  }
541
646
  } else if (!this.options.flex) {
@@ -548,6 +653,7 @@ var parser$1 = function () {
548
653
  if (token !== false) {
549
654
  return token;
550
655
  }
656
+ // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
551
657
  return false;
552
658
  }
553
659
  if (this._input === "") {
@@ -560,6 +666,7 @@ var parser$1 = function () {
560
666
  });
561
667
  }
562
668
  },
669
+ // return next match that has a token
563
670
  lex: function lex() {
564
671
  var r = this.next();
565
672
  if (r) {
@@ -568,9 +675,11 @@ var parser$1 = function () {
568
675
  return this.lex();
569
676
  }
570
677
  },
678
+ // activates a new lexer condition state (pushes the new lexer condition state onto the condition stack)
571
679
  begin: function begin(condition) {
572
680
  this.conditionStack.push(condition);
573
681
  },
682
+ // pop the previously active lexer condition state off the condition stack
574
683
  popState: function popState() {
575
684
  var n = this.conditionStack.length - 1;
576
685
  if (n > 0) {
@@ -579,6 +688,7 @@ var parser$1 = function () {
579
688
  return this.conditionStack[0];
580
689
  }
581
690
  },
691
+ // produce the lexer rule set which is active for the currently active lexer condition state
582
692
  _currentRules: function _currentRules() {
583
693
  if (this.conditionStack.length && this.conditionStack[this.conditionStack.length - 1]) {
584
694
  return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules;
@@ -586,6 +696,7 @@ var parser$1 = function () {
586
696
  return this.conditions["INITIAL"].rules;
587
697
  }
588
698
  },
699
+ // return the currently active lexer condition state; when an index argument is provided it produces the N-th previous condition state, if available
589
700
  topState: function topState(n) {
590
701
  n = this.conditionStack.length - 1 - Math.abs(n || 0);
591
702
  if (n >= 0) {
@@ -594,9 +705,11 @@ var parser$1 = function () {
594
705
  return "INITIAL";
595
706
  }
596
707
  },
708
+ // alias for begin(condition)
597
709
  pushState: function pushState(condition) {
598
710
  this.begin(condition);
599
711
  },
712
+ // return the number of states currently on the stack
600
713
  stateStackSize: function stateStackSize() {
601
714
  return this.conditionStack.length;
602
715
  },
@@ -628,6 +741,7 @@ var parser$1 = function () {
628
741
  case 11:
629
742
  return 17;
630
743
  case 12:
744
+ /* skip whitespace */
631
745
  break;
632
746
  case 13:
633
747
  return 6;
@@ -653,10 +767,85 @@ var parser$1 = function () {
653
767
  }();
654
768
  const unitParser = parser$1;
655
769
 
770
+ // This is a @generated file
771
+
772
+ /* parser generated by jison 0.4.15 */
773
+ /*
774
+ Returns a Parser object of the following structure:
775
+
776
+ Parser: {
777
+ yy: {}
778
+ }
779
+
780
+ Parser.prototype: {
781
+ yy: {},
782
+ trace: function(),
783
+ symbols_: {associative list: name ==> number},
784
+ terminals_: {associative list: number ==> name},
785
+ productions_: [...],
786
+ performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$),
787
+ table: [...],
788
+ defaultActions: {...},
789
+ parseError: function(str, hash),
790
+ parse: function(input),
791
+
792
+ lexer: {
793
+ EOF: 1,
794
+ parseError: function(str, hash),
795
+ setInput: function(input),
796
+ input: function(),
797
+ unput: function(str),
798
+ more: function(),
799
+ less: function(n),
800
+ pastInput: function(),
801
+ upcomingInput: function(),
802
+ showPosition: function(),
803
+ test_match: function(regex_match_array, rule_index),
804
+ next: function(),
805
+ lex: function(),
806
+ begin: function(condition),
807
+ popState: function(),
808
+ _currentRules: function(),
809
+ topState: function(),
810
+ pushState: function(condition),
811
+
812
+ options: {
813
+ ranges: boolean (optional: true ==> token location info will include a .range[] member)
814
+ flex: boolean (optional: true ==> flex-like lexing behaviour where the rules are tested exhaustively to find the longest match)
815
+ backtrack_lexer: boolean (optional: true ==> lexer regexes are tested in order and for each matching regex the action code is invoked; the lexer terminates the scan when a token is returned by the action code)
816
+ },
817
+
818
+ performAction: function(yy, yy_, $avoiding_name_collisions, YY_START),
819
+ rules: [...],
820
+ conditions: {associative list: name ==> set},
821
+ }
822
+ }
823
+
824
+
825
+ token location info (@$, _$, etc.): {
826
+ first_line: n,
827
+ last_line: n,
828
+ first_column: n,
829
+ last_column: n,
830
+ range: [start_number, end_number] (where the numbers are indexes into the input string, regular zero-based)
831
+ }
832
+
833
+
834
+ the parseError function receives a 'hash' object with these members for lexer and parser errors: {
835
+ text: (matched text)
836
+ token: (the produced terminal token, if any)
837
+ line: (yylineno)
838
+ }
839
+ while parser (grammar) errors will also provide these members, i.e. parser errors deliver a superset of attributes: {
840
+ loc: (yylloc)
841
+ expected: (string describing the set of expected tokens)
842
+ recoverable: (boolean: TRUE when the parser has a error recovery rule available for this particular error)
843
+ }
844
+ */
656
845
  var parser = function () {
657
- var o = function o(k, v, _o, l) {
658
- for (_o = _o || {}, l = k.length; l--; _o[k[l]] = v);
659
- return _o;
846
+ var o = function (k, v, o, l) {
847
+ for (o = o || {}, l = k.length; l--; o[k[l]] = v);
848
+ return o;
660
849
  },
661
850
  $V0 = [1, 7],
662
851
  $V1 = [1, 17],
@@ -768,7 +957,9 @@ var parser = function () {
768
957
  46: "FRAC"
769
958
  },
770
959
  productions_: [0, [3, 4], [3, 2], [3, 1], [4, 1], [7, 3], [7, 3], [7, 1], [9, 2], [9, 3], [9, 3], [9, 1], [13, 2], [13, 1], [15, 1], [17, 1], [17, 3], [17, 1], [20, 1], [20, 1], [20, 3], [11, 2], [11, 2], [11, 1], [25, 3], [25, 1], [27, 1], [24, 3], [24, 1], [24, 1], [24, 1], [24, 1], [24, 3], [24, 3], [36, 1], [38, 4], [38, 4], [38, 7], [38, 4], [38, 3], [38, 3], [38, 4], [26, 1], [26, 1], [26, 7]],
771
- performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$) {
960
+ performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) {
961
+ /* this == yyval */
962
+
772
963
  var $0 = $$.length - 1;
773
964
  switch (yystate) {
774
965
  case 1:
@@ -1814,6 +2005,7 @@ var parser = function () {
1814
2005
  } else {
1815
2006
  this.parseError = Object.getPrototypeOf(this).parseError;
1816
2007
  }
2008
+ //_token_stack:
1817
2009
  function lex() {
1818
2010
  var token;
1819
2011
  token = lexer.lex() || EOF;
@@ -1913,6 +2105,7 @@ var parser = function () {
1913
2105
  return true;
1914
2106
  }
1915
2107
  };
2108
+ /* generated by jison-lex 0.3.4 */
1916
2109
  var lexer = function () {
1917
2110
  var lexer = {
1918
2111
  EOF: 1,
@@ -1923,6 +2116,7 @@ var parser = function () {
1923
2116
  throw new Error(str);
1924
2117
  }
1925
2118
  },
2119
+ // resets the lexer, sets new input
1926
2120
  setInput: function (input, yy) {
1927
2121
  this.yy = yy || this.yy || {};
1928
2122
  this._input = input;
@@ -1942,6 +2136,7 @@ var parser = function () {
1942
2136
  this.offset = 0;
1943
2137
  return this;
1944
2138
  },
2139
+ // consumes and returns one char from the input
1945
2140
  input: function () {
1946
2141
  var ch = this._input[0];
1947
2142
  this.yytext += ch;
@@ -1962,11 +2157,13 @@ var parser = function () {
1962
2157
  this._input = this._input.slice(1);
1963
2158
  return ch;
1964
2159
  },
2160
+ // unshifts one char (or a string) into the input
1965
2161
  unput: function (ch) {
1966
2162
  var len = ch.length;
1967
2163
  var lines = ch.split(/(?:\r\n?|\n)/g);
1968
2164
  this._input = ch + this._input;
1969
2165
  this.yytext = this.yytext.substr(0, this.yytext.length - len);
2166
+ //this.yyleng -= len;
1970
2167
  this.offset -= len;
1971
2168
  var oldLines = this.match.split(/(?:\r\n?|\n)/g);
1972
2169
  this.match = this.match.substr(0, this.match.length - 1);
@@ -1987,10 +2184,12 @@ var parser = function () {
1987
2184
  this.yyleng = this.yytext.length;
1988
2185
  return this;
1989
2186
  },
2187
+ // When called from action, caches matched text and appends it on next action
1990
2188
  more: function () {
1991
2189
  this._more = true;
1992
2190
  return this;
1993
2191
  },
2192
+ // When called from action, signals the lexer that this rule fails to match the input, so the next matching rule (regex) should be tested instead.
1994
2193
  reject: function () {
1995
2194
  if (this.options.backtrack_lexer) {
1996
2195
  this._backtrack = true;
@@ -2003,13 +2202,16 @@ var parser = function () {
2003
2202
  }
2004
2203
  return this;
2005
2204
  },
2205
+ // retain first n characters of the match
2006
2206
  less: function (n) {
2007
2207
  this.unput(this.match.slice(n));
2008
2208
  },
2209
+ // displays already matched input, i.e. for error messages
2009
2210
  pastInput: function () {
2010
2211
  var past = this.matched.substr(0, this.matched.length - this.match.length);
2011
2212
  return (past.length > 20 ? '...' : '') + past.substr(-20).replace(/\n/g, "");
2012
2213
  },
2214
+ // displays upcoming input, i.e. for error messages
2013
2215
  upcomingInput: function () {
2014
2216
  var next = this.match;
2015
2217
  if (next.length < 20) {
@@ -2017,14 +2219,17 @@ var parser = function () {
2017
2219
  }
2018
2220
  return (next.substr(0, 20) + (next.length > 20 ? '...' : '')).replace(/\n/g, "");
2019
2221
  },
2222
+ // displays the character position where the lexing error occurred, i.e. for error messages
2020
2223
  showPosition: function () {
2021
2224
  var pre = this.pastInput();
2022
2225
  var c = new Array(pre.length + 1).join("-");
2023
2226
  return pre + this.upcomingInput() + "\n" + c + "^";
2024
2227
  },
2228
+ // test the lexed token: return FALSE when not a match, otherwise return token
2025
2229
  test_match: function (match, indexed_rule) {
2026
2230
  var token, lines, backup;
2027
2231
  if (this.options.backtrack_lexer) {
2232
+ // save context
2028
2233
  backup = {
2029
2234
  yylineno: this.yylineno,
2030
2235
  yylloc: {
@@ -2077,13 +2282,15 @@ var parser = function () {
2077
2282
  if (token) {
2078
2283
  return token;
2079
2284
  } else if (this._backtrack) {
2285
+ // recover context
2080
2286
  for (var k in backup) {
2081
2287
  this[k] = backup[k];
2082
2288
  }
2083
- return false;
2289
+ return false; // rule action called reject() implying the next rule should be tested instead.
2084
2290
  }
2085
2291
  return false;
2086
2292
  },
2293
+ // return next match in input
2087
2294
  next: function () {
2088
2295
  if (this.done) {
2089
2296
  return this.EOF;
@@ -2108,8 +2315,9 @@ var parser = function () {
2108
2315
  return token;
2109
2316
  } else if (this._backtrack) {
2110
2317
  match = false;
2111
- continue;
2318
+ continue; // rule action called reject() implying a rule MISmatch.
2112
2319
  } else {
2320
+ // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
2113
2321
  return false;
2114
2322
  }
2115
2323
  } else if (!this.options.flex) {
@@ -2122,6 +2330,7 @@ var parser = function () {
2122
2330
  if (token !== false) {
2123
2331
  return token;
2124
2332
  }
2333
+ // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
2125
2334
  return false;
2126
2335
  }
2127
2336
  if (this._input === "") {
@@ -2134,6 +2343,7 @@ var parser = function () {
2134
2343
  });
2135
2344
  }
2136
2345
  },
2346
+ // return next match that has a token
2137
2347
  lex: function lex() {
2138
2348
  var r = this.next();
2139
2349
  if (r) {
@@ -2142,9 +2352,11 @@ var parser = function () {
2142
2352
  return this.lex();
2143
2353
  }
2144
2354
  },
2355
+ // activates a new lexer condition state (pushes the new lexer condition state onto the condition stack)
2145
2356
  begin: function begin(condition) {
2146
2357
  this.conditionStack.push(condition);
2147
2358
  },
2359
+ // pop the previously active lexer condition state off the condition stack
2148
2360
  popState: function popState() {
2149
2361
  var n = this.conditionStack.length - 1;
2150
2362
  if (n > 0) {
@@ -2153,6 +2365,7 @@ var parser = function () {
2153
2365
  return this.conditionStack[0];
2154
2366
  }
2155
2367
  },
2368
+ // produce the lexer rule set which is active for the currently active lexer condition state
2156
2369
  _currentRules: function _currentRules() {
2157
2370
  if (this.conditionStack.length && this.conditionStack[this.conditionStack.length - 1]) {
2158
2371
  return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules;
@@ -2160,6 +2373,7 @@ var parser = function () {
2160
2373
  return this.conditions["INITIAL"].rules;
2161
2374
  }
2162
2375
  },
2376
+ // return the currently active lexer condition state; when an index argument is provided it produces the N-th previous condition state, if available
2163
2377
  topState: function topState(n) {
2164
2378
  n = this.conditionStack.length - 1 - Math.abs(n || 0);
2165
2379
  if (n >= 0) {
@@ -2168,9 +2382,11 @@ var parser = function () {
2168
2382
  return "INITIAL";
2169
2383
  }
2170
2384
  },
2385
+ // alias for begin(condition)
2171
2386
  pushState: function pushState(condition) {
2172
2387
  this.begin(condition);
2173
2388
  },
2389
+ // return the number of states currently on the stack
2174
2390
  stateStackSize: function stateStackSize() {
2175
2391
  return this.conditionStack.length;
2176
2392
  },
@@ -2180,10 +2396,13 @@ var parser = function () {
2180
2396
  performAction: function anonymous(yy, yy_, $avoiding_name_collisions, YY_START) {
2181
2397
  switch ($avoiding_name_collisions) {
2182
2398
  case 0:
2399
+ /* skip whitespace */
2183
2400
  break;
2184
2401
  case 1:
2402
+ /* skip \space */
2185
2403
  break;
2186
2404
  case 2:
2405
+ /* skip '\ ' */
2187
2406
  break;
2188
2407
  case 3:
2189
2408
  return "INT";
@@ -2405,20 +2624,65 @@ var parser = function () {
2405
2624
  return new Parser();
2406
2625
  }();
2407
2626
 
2408
- var _class5, _class12;
2409
- const isNaN = function isNaN(object) {
2627
+ /* eslint-disable prettier/prettier */
2628
+ /* eslint-disable import/order */
2629
+ /* TODO: fix these lint errors (http://eslint.org/docs/rules): */
2630
+ /* eslint-disable indent, no-undef, no-var, no-dupe-keys, no-new-func, no-redeclare, comma-dangle, max-len, prefer-spread, space-infix-ops, space-unary-ops */
2631
+
2632
+ /* The node hierarcy is as follows:
2633
+
2634
+ (Expr)
2635
+ (Seq) 2+ children
2636
+ Add
2637
+ Mul
2638
+ Pow 2 children
2639
+ Log 2 children
2640
+ Eq 2 children
2641
+ Trig 1 child
2642
+ Abs 1 child
2643
+ (Sym)
2644
+ Func 1 child e.g. f(x)
2645
+ Var leaf node e.g. x, x_n
2646
+ Const leaf node e.g. pi, e, <i>
2647
+ Unit leaf node e.g. kg
2648
+ (Num) leaf node
2649
+ Rational e.g. 2/3
2650
+ Int
2651
+ Float
2652
+
2653
+ (abstract, not meant to be instantiated)
2654
+
2655
+ == Key design concepts ==
2656
+ Functional: All methods return new nodes - nodes are never mutated.
2657
+ Ignore commutativity: Commutative inputs should be parsed equivalently.
2658
+ Exploit commutativity: Output should take advantage of ordering.
2659
+ */
2660
+
2661
+ /* non user-facing functions */
2662
+
2663
+ // reliably detect NaN
2664
+ const isNaN = function (object) {
2410
2665
  return object !== object;
2411
2666
  };
2412
- const randomFloat = function randomFloat(min, max) {
2667
+
2668
+ // return a random float between min (inclusive) and max (exclusive),
2669
+ // not that inclusivity means much, probabilistically, on floats
2670
+ const randomFloat = function (min, max) {
2413
2671
  var extent = max - min;
2414
2672
  return Math.random() * extent + min;
2415
2673
  };
2674
+
2675
+ /* constants */
2416
2676
  var ITERATIONS = 12;
2417
- var TOLERANCE = 9;
2677
+ var TOLERANCE = 9; // decimal places
2678
+
2679
+ // NOTE(kevinb): _.partition exists in a more recent version of underscore.
2680
+ // To avoid having to update underscore I've added a hacky version of this
2681
+ // method here.
2418
2682
  function partition(list, iteratee) {
2419
2683
  const a = [];
2420
2684
  const b = [];
2421
- _.forEach(list, (elem, key, ctx) => {
2685
+ ___default.default.forEach(list, (elem, key, ctx) => {
2422
2686
  if (iteratee(elem, key, ctx)) {
2423
2687
  a.push(elem);
2424
2688
  } else {
@@ -2430,7 +2694,7 @@ function partition(list, iteratee) {
2430
2694
  function isExpr(arg) {
2431
2695
  return arg instanceof Expr;
2432
2696
  }
2433
- const isAdd = function isAdd(term) {
2697
+ const isAdd = function (term) {
2434
2698
  return term instanceof Add;
2435
2699
  };
2436
2700
  function isRational(arg) {
@@ -2443,13 +2707,23 @@ function getFactors(expr) {
2443
2707
  return [expr];
2444
2708
  }
2445
2709
  }
2710
+ /* abstract base expression node */
2446
2711
  class Expr {
2712
+ hints;
2447
2713
  constructor() {
2448
- this.hints = void 0;
2449
2714
  this.hints = {
2450
2715
  parens: false
2451
2716
  };
2452
2717
  }
2718
+
2719
+ // this node's immediate constructor
2720
+ // The `new (...args: any[]): any;` part of the type is a
2721
+ // "construct" signature. It indicates that `func` is a class.
2722
+ // See https://www.typescriptlang.org/docs/handbook/2/functions.html#construct-signatures.
2723
+
2724
+ // an array of the arguments to this node's immediate constructor
2725
+
2726
+ // make a new node with the given arguments
2453
2727
  construct(args) {
2454
2728
  const func = this.func;
2455
2729
  const instance = new func(...args);
@@ -2458,29 +2732,59 @@ class Expr {
2458
2732
  }
2459
2733
  return instance;
2460
2734
  }
2461
- recurse(method, ...passed) {
2735
+
2736
+ // an abstraction for chainable, bottom-up recursion
2737
+ // NOTE(kevinb): This method is highly dynamic. It's possible that it
2738
+ // could be made more type-safe using overload signatures.
2739
+ recurse(method) {
2740
+ for (var _len = arguments.length, passed = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
2741
+ passed[_key - 1] = arguments[_key];
2742
+ }
2462
2743
  var args = this.args().map(function (arg) {
2463
- return _.isString(arg) || _.isNumber(arg) ? arg : arg == null ? void 0 : arg[method].apply(arg, passed);
2744
+ return ___default.default.isString(arg) || ___default.default.isNumber(arg) ? arg : arg?.[method].apply(arg, passed);
2464
2745
  });
2465
2746
  return this.construct(args);
2466
2747
  }
2467
- eval(vars = {}, options) {
2468
- throw new Error("Abstract method - must override for expr: " + this.print());
2748
+
2749
+ // evaluate numerically with given variable mapping
2750
+ // NOTE(kevin): This could made into an abstract method but
2751
+ // Eq doesn't implement it. This indicates that we probably
2752
+ // need to introduce another class in our hierarchy.
2753
+ eval() {
2754
+ throw new Error("Abstract method - must override for expr: " +
2755
+ // eslint-disable-next-line @babel/no-invalid-this
2756
+ this.print());
2469
2757
  }
2758
+
2759
+ // NOTE(kevin): This could made into an abstract method but
2760
+ // Eq doesn't implement it. This indicates that we probably
2761
+ // need to introduce another class in our hierarchy.
2470
2762
  codegen() {
2471
- throw new Error("Abstract method - must override for expr: " + this.print());
2763
+ throw new Error("Abstract method - must override for expr: " +
2764
+ // eslint-disable-next-line @babel/no-invalid-this
2765
+ this.print());
2472
2766
  }
2473
2767
  compile() {
2474
2768
  var code = this.codegen();
2475
2769
  try {
2770
+ // @ts-expect-error: TypeScript doesn't want to unify
2771
+ // `Function` with the `compile`'s return type.
2476
2772
  return new Function("vars", "return " + code + ";");
2477
- } catch (_unused) {
2773
+ } catch {
2478
2774
  throw new Error("Function did not compile: " + code);
2479
2775
  }
2480
2776
  }
2777
+
2778
+ // returns a string unambiguously representing the expression
2779
+ // should be valid as input
2780
+ // e.g. this.equals(parse(this.print())) === true
2781
+
2782
+ // returns a TeX string representing the expression
2783
+
2784
+ // returns a TeX string, modified by the given options
2481
2785
  asTex(options) {
2482
2786
  options = options || {};
2483
- _.defaults(options, {
2787
+ ___default.default.defaults(options, {
2484
2788
  display: true,
2485
2789
  dynamic: true,
2486
2790
  times: false
@@ -2498,46 +2802,76 @@ class Expr {
2498
2802
  }
2499
2803
  return tex;
2500
2804
  }
2805
+
2806
+ // returns the name of this expression's constructor as a string
2807
+ // only used for testing and debugging
2501
2808
  name() {
2502
2809
  return this.func.name;
2503
2810
  }
2811
+
2812
+ // returns a string representing current node structure
2504
2813
  repr() {
2505
2814
  return this.name() + "(" + this.args().map(function (arg) {
2506
- return _.isString(arg) || _.isNumber(arg) ? arg : arg == null ? void 0 : arg.repr();
2815
+ return ___default.default.isString(arg) || ___default.default.isNumber(arg) ? arg : arg?.repr();
2507
2816
  }).join(",") + ")";
2508
2817
  }
2818
+
2819
+ // removes all negative signs
2509
2820
  strip() {
2510
2821
  return this.recurse("strip");
2511
2822
  }
2823
+
2824
+ // canonically reorders all commutative elements
2512
2825
  normalize() {
2513
2826
  return this.recurse("normalize");
2514
2827
  }
2828
+
2829
+ // expands the expression
2515
2830
  expand() {
2516
2831
  return this.recurse("expand");
2517
2832
  }
2833
+
2834
+ // naively factors out like terms
2518
2835
  factor(options) {
2519
2836
  return this.recurse("factor", options);
2520
2837
  }
2838
+
2839
+ // collect all like terms
2521
2840
  collect(options) {
2522
2841
  return this.recurse("collect", options);
2523
2842
  }
2843
+
2844
+ // strict syntactic equality check
2524
2845
  equals(other) {
2525
2846
  return this.normalize().print() === other.normalize().print();
2526
2847
  }
2848
+
2849
+ // expand and collect until the expression no longer changes
2527
2850
  simplify(options) {
2528
- options = _extends({
2529
- once: false
2530
- }, options);
2851
+ options = {
2852
+ once: false,
2853
+ ...options
2854
+ };
2855
+
2856
+ // Attempt to factor and collect
2531
2857
  var step1 = this.factor(options);
2532
2858
  var step2 = step1.collect(options);
2859
+
2860
+ // Rollback if collect didn't do anything
2533
2861
  if (step1.equals(step2)) {
2534
2862
  step2 = this.collect(options);
2535
2863
  }
2864
+
2865
+ // Attempt to expand and collect
2536
2866
  var step3 = step2.expand();
2537
2867
  var step4 = step3.collect(options);
2868
+
2869
+ // Rollback if collect didn't do anything
2538
2870
  if (step3.equals(step4)) {
2539
2871
  step4 = step2.collect(options);
2540
2872
  }
2873
+
2874
+ // One round of simplification complete
2541
2875
  var simplified = step4;
2542
2876
  if (options.once || this.equals(simplified)) {
2543
2877
  return simplified;
@@ -2545,35 +2879,52 @@ class Expr {
2545
2879
  return simplified.simplify(options);
2546
2880
  }
2547
2881
  }
2882
+
2883
+ // check whether this expression is simplified
2548
2884
  isSimplified() {
2549
2885
  return this.equals(this.simplify());
2550
2886
  }
2887
+
2888
+ // return the child nodes of this node
2551
2889
  exprArgs() {
2890
+ // @ts-expect-error: Type 'string | number | Expr | undefined' is not assignable to type 'string | Expr'.
2552
2891
  return this.args().filter(isExpr);
2553
2892
  }
2893
+
2894
+ // return the variables (function and non) within the expression
2554
2895
  getVars(excludeFunc) {
2555
- return _.uniq(_.flatten(_.invoke(this.exprArgs(), "getVars", excludeFunc))).sort();
2896
+ return ___default.default.uniq(___default.default.flatten(___default.default.invoke(this.exprArgs(), "getVars", excludeFunc))).sort();
2556
2897
  }
2557
2898
  getConsts() {
2558
- return _.uniq(_.flatten(_.invoke(this.exprArgs(), "getConsts"))).sort();
2899
+ return ___default.default.uniq(___default.default.flatten(___default.default.invoke(this.exprArgs(), "getConsts"))).sort();
2559
2900
  }
2560
2901
  getUnits() {
2561
- return _.flatten(_.invoke(this.exprArgs(), "getUnits"));
2902
+ return ___default.default.flatten(___default.default.invoke(this.exprArgs(), "getUnits"));
2562
2903
  }
2904
+
2905
+ // check whether this expression node is of a particular type
2563
2906
  is(func) {
2564
2907
  return this instanceof func;
2565
2908
  }
2909
+
2910
+ // check whether this expression has a particular node type
2566
2911
  has(func) {
2567
2912
  if (this instanceof func) {
2568
2913
  return true;
2569
2914
  }
2570
- return _.any(this.exprArgs(), function (arg) {
2915
+ return ___default.default.any(this.exprArgs(), function (arg) {
2571
2916
  return arg.has(func);
2572
2917
  });
2573
2918
  }
2919
+
2920
+ // raise this expression to a given exponent
2921
+ // most useful for eventually implementing i^3 = -i, etc.
2574
2922
  raiseToThe(exp, options) {
2575
2923
  return new Pow(this, exp);
2576
2924
  }
2925
+
2926
+ // does this expression have a specific rendering hint?
2927
+ // rendering hints are picked up while parsing, but are lost during transformations
2577
2928
  isSubtract() {
2578
2929
  return false;
2579
2930
  }
@@ -2583,17 +2934,25 @@ class Expr {
2583
2934
  isRoot() {
2584
2935
  return false;
2585
2936
  }
2937
+
2938
+ // whether this node needs an explicit multiplication sign if following a Num
2586
2939
  needsExplicitMul() {
2587
2940
  return this.exprArgs()[0].needsExplicitMul();
2588
2941
  }
2942
+
2943
+ // check that the variables in both expressions are the same
2589
2944
  sameVars(other) {
2590
2945
  var vars1 = this.getVars();
2591
2946
  var vars2 = other.getVars();
2592
- var same = function same(array1, array2) {
2593
- return !_.difference(array1, array2).length;
2947
+
2948
+ // the other Expr can have more variables than this one
2949
+ // this lets you multiply equations by other variables
2950
+ var same = function (array1, array2) {
2951
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
2952
+ return !___default.default.difference(array1, array2).length;
2594
2953
  };
2595
- var lower = function lower(array) {
2596
- return _.uniq(_.invoke(array, "toLowerCase")).sort();
2954
+ var lower = function (array) {
2955
+ return ___default.default.uniq(___default.default.invoke(array, "toLowerCase")).sort();
2597
2956
  };
2598
2957
  var equal = same(vars1, vars2);
2599
2958
  var equalIgnoringCase = same(lower(vars1), lower(vars2));
@@ -2602,39 +2961,86 @@ class Expr {
2602
2961
  equalIgnoringCase: equalIgnoringCase
2603
2962
  };
2604
2963
  }
2964
+
2965
+ // semantic equality check, call after sameVars() to avoid potential false positives
2966
+ // plug in random numbers for the variables in both expressions
2967
+ // if they both consistently evaluate the same, then they're the same
2605
2968
  compare(other) {
2969
+ // equation comparisons are handled by Eq.compare()
2606
2970
  if (other instanceof Eq) {
2607
2971
  return false;
2608
2972
  }
2609
- var varList = _.union(this.getVars(true), other.getVars(true));
2610
- var getDelta = function getDelta(num1, num2) {
2973
+ var varList = ___default.default.union(this.getVars(/* excludeFunc */true), other.getVars(/* excludeFunc */true));
2974
+
2975
+ // If the numbers are large we would like to do a relative comparison
2976
+ // rather than an absolute one, but if they're small enough then an
2977
+ // absolute comparison makes more sense
2978
+ var getDelta = function (num1, num2) {
2611
2979
  if (Math.abs(num1) < 1 || Math.abs(num2) < 1) {
2612
2980
  return Math.abs(num1 - num2);
2613
2981
  } else {
2614
2982
  return Math.abs(1 - num1 / num2);
2615
2983
  }
2616
2984
  };
2617
- var equalNumbers = function equalNumbers(num1, num2) {
2985
+ var equalNumbers = function (num1, num2) {
2618
2986
  var delta = getDelta(num1, num2);
2619
- return num1 === num2 || isNaN(num1) && isNaN(num2) || delta < Math.pow(10, -9);
2987
+ return num1 === num2 /* needed if either is +/- Infinity */ || isNaN(num1) && isNaN(num2) || delta < Math.pow(10, -9);
2620
2988
  };
2621
- const varAndFuncList = _.union(this.getVars(false), other.getVars(false));
2989
+
2990
+ // If no variables, only need to evaluate once.
2991
+ // note(matthew) Seems to be an optimization for simple cases like `2+2=4`
2992
+ // where there are no variables / functions.
2993
+ // Ran into issues with it in LEMS-2777 and found that tests pass
2994
+ // with this removed, but keeping a modified version out of caution.
2995
+ const varAndFuncList = ___default.default.union(this.getVars(/* excludeFunc */false), other.getVars(/* excludeFunc */false));
2996
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
2622
2997
  if (!varAndFuncList.length && !this.has(Unit) && !other.has(Unit)) {
2623
2998
  return equalNumbers(this.eval(), other.eval());
2624
2999
  }
3000
+
3001
+ // collect here to avoid sometimes dividing by zero, and sometimes not
3002
+ // it is better to be deterministic, e.g. x/x -> 1
3003
+ // TODO(alex): may want to keep track of assumptions as they're made
2625
3004
  var expr1 = this.collect();
2626
3005
  var expr2 = other.collect();
2627
3006
  var unitList1 = this.getUnits();
2628
3007
  var unitList2 = other.getUnits();
2629
- if (!_.isEqual(unitList1, unitList2)) {
3008
+ if (!___default.default.isEqual(unitList1, unitList2)) {
2630
3009
  return false;
2631
3010
  }
3011
+
3012
+ // Compare at a set number (currently 12) of points to determine
3013
+ // equality.
3014
+ //
3015
+ // `range` (and `vars`) is the only variable that varies through the
3016
+ // iterations. For each of range = 10, 100, and 1000, each random
3017
+ // variable is picked from (-range, range).
3018
+ //
3019
+ // Note that because there are 12 iterations and three ranges, each
3020
+ // range is checked four times.
2632
3021
  for (var i = 0; i < ITERATIONS; i++) {
2633
3022
  var vars = {};
3023
+
3024
+ // One third total iterations each with range 10, 100, and 1000
2634
3025
  var range = Math.pow(10, 1 + Math.floor(3 * i / ITERATIONS));
3026
+
3027
+ // Half of the iterations should only use integer values.
3028
+ // This is because expressions like (-2)^x are common but result
3029
+ // in NaN when evaluated in JS with non-integer values of x.
3030
+ // Without this, (-2)^x and (-2)^(x+1) both end up always being NaN
3031
+ // and thus equivalent. With this, the most common failure case is
3032
+ // avoided. However, less common cases such as (-2)^(x+0.1) and
3033
+ // (-2)^(x+1.1) will still both evaluate to NaN and result in a
3034
+ // false positive.
3035
+ //
3036
+ // Note that the above is only true in vanilla JS Number-land,
3037
+ // which has no concept of complex numbers. The solution is simple:
3038
+ // Integrate a library for handling complex numbers.
3039
+ //
3040
+ // TODO(alex): Add support for complex numbers, then remove this.
2635
3041
  var useFloats = i % 2 === 0;
2636
- _.each(varList, function (v) {
2637
- vars[v] = useFloats ? randomFloat(-range, range) : _.random(-range, range);
3042
+ ___default.default.each(varList, function (v) {
3043
+ vars[v] = useFloats ? randomFloat(-range, range) : ___default.default.random(-range, range);
2638
3044
  });
2639
3045
  let equal;
2640
3046
  if (expr1.has(Func) || expr2.has(Func) || expr1.has(Unit) || expr2.has(Unit)) {
@@ -2652,6 +3058,8 @@ class Expr {
2652
3058
  }
2653
3059
  return true;
2654
3060
  }
3061
+
3062
+ // evaluate as much of the expression as possible
2655
3063
  partialEval(vars) {
2656
3064
  if (this instanceof Unit) {
2657
3065
  return this;
@@ -2663,50 +3071,86 @@ class Expr {
2663
3071
  return this.recurse("partialEval", vars);
2664
3072
  }
2665
3073
  }
3074
+
3075
+ // check that the structure of both expressions is the same
3076
+ // all negative signs are stripped and the expressions are converted to
3077
+ // a canonical commutative form
3078
+ // should only be done after compare() returns true to avoid false positives
2666
3079
  sameForm(other) {
2667
3080
  return this.strip().equals(other.strip());
2668
3081
  }
3082
+
3083
+ // returns the GCD of this expression and the given factor
2669
3084
  findGCD(factor) {
2670
3085
  return this.equals(factor) ? factor : NumOne;
2671
3086
  }
3087
+
3088
+ // return this expression's denominator
2672
3089
  getDenominator() {
2673
3090
  return NumOne;
2674
3091
  }
3092
+
3093
+ // return this expression as a Mul
2675
3094
  asMul() {
2676
3095
  return new Mul(NumOne, this);
2677
3096
  }
3097
+
3098
+ // TODO(alex): rename to isDefinitePositive or similar?
3099
+ // return whether this expression is 100% positive
2678
3100
  isPositive() {
2679
- throw new Error("Abstract method - must override for expr: " + this.print());
3101
+ throw new Error("Abstract method - must override for expr: " +
3102
+ // eslint-disable-next-line @babel/no-invalid-this
3103
+ this.print());
2680
3104
  }
3105
+
3106
+ // TODO(alex): rename to hasNegativeSign or similar?
3107
+ // return whether this expression has a negative sign
2681
3108
  isNegative() {
2682
3109
  return false;
2683
3110
  }
3111
+
3112
+ // return a factor of this expression that is 100% positive
2684
3113
  asPositiveFactor() {
2685
3114
  return this.isPositive() ? this : NumOne;
2686
3115
  }
3116
+
3117
+ // return a copy of the expression with a new hint set (preserves hints)
2687
3118
  addHint(hint) {
2688
3119
  if (!hint) {
2689
3120
  return this;
2690
3121
  }
2691
3122
  var expr = this.construct(this.args());
2692
- expr.hints = _.clone(this.hints);
3123
+ expr.hints = ___default.default.clone(this.hints);
2693
3124
  expr.hints[hint] = true;
2694
3125
  return expr;
2695
3126
  }
3127
+
3128
+ // complete parse by performing a few necessary transformations
2696
3129
  completeParse() {
2697
3130
  return this.recurse("completeParse");
2698
3131
  }
2699
3132
  abs() {
2700
- throw new Error("Abstract method - must override for expr: " + this.print());
3133
+ throw new Error("Abstract method - must override for expr: " +
3134
+ // eslint-disable-next-line @babel/no-invalid-this
3135
+ this.print());
2701
3136
  }
2702
3137
  negate() {
2703
3138
  return new Mul(NumNeg, this);
2704
3139
  }
2705
3140
  }
3141
+
3142
+ /* abstract sequence node */
2706
3143
  class Seq extends Expr {
2707
- constructor(...args) {
3144
+ // This should always have at least two terms.
3145
+ // TODO(kevinb): Try enforcing this at the type-level using [T, T, T[]]
3146
+ terms;
3147
+
3148
+ // TODO(kevinb): Update this use `...args: Expr[]`
3149
+ constructor() {
2708
3150
  super();
2709
- this.terms = void 0;
3151
+ for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
3152
+ args[_key2] = arguments[_key2];
3153
+ }
2710
3154
  if (args.length === 1) {
2711
3155
  this.terms = args[0];
2712
3156
  } else {
@@ -2717,7 +3161,7 @@ class Seq extends Expr {
2717
3161
  return this.terms;
2718
3162
  }
2719
3163
  normalize() {
2720
- var terms = _.sortBy(_.invoke(this.terms, "normalize"), term => {
3164
+ var terms = ___default.default.sortBy(___default.default.invoke(this.terms, "normalize"), term => {
2721
3165
  return term.print();
2722
3166
  });
2723
3167
  return new this.func(terms);
@@ -2725,35 +3169,54 @@ class Seq extends Expr {
2725
3169
  expand() {
2726
3170
  return this.recurse("expand").flatten();
2727
3171
  }
3172
+
3173
+ // partition the sequence into its numeric and non-numeric parts
3174
+ // makes no guarantees about the validity of either part!
2728
3175
  partition() {
2729
3176
  var [numbers, others] = partition(this.terms, term => {
2730
3177
  return term instanceof Num;
2731
3178
  });
2732
3179
  return [new this.func(numbers), new this.func(others)];
2733
3180
  }
3181
+
3182
+ // ensure that sequences have 2+ terms and no nested sequences of the same type
3183
+ // this is a shallow flattening and will return a non-Seq if terms.length <= 1
2734
3184
  flatten() {
2735
3185
  var type = this;
2736
- var terms = _.reject(this.terms, term => {
3186
+ var terms = ___default.default.reject(this.terms, term => {
3187
+ // @ts-expect-error: `identity` is defined on Add and Mul but doesn't
3188
+ // exist on Seq itself.
2737
3189
  return term.equals(type.identity);
2738
3190
  });
2739
3191
  if (terms.length === 0) {
3192
+ // @ts-expect-error: `identity` is defined on Add and Mul but doesn't
3193
+ // exist on Seq itself.
2740
3194
  return type.identity;
2741
3195
  }
2742
3196
  if (terms.length === 1) {
2743
3197
  return terms[0];
2744
3198
  }
3199
+
3200
+ // same contains the children which are Seqs of the same type as this Seq
2745
3201
  const [same, others] = partition(terms, term => {
2746
3202
  return term instanceof type.func;
2747
3203
  });
2748
- var flattened = others.concat(_.flatten(_.pluck(same, "terms"), true));
3204
+ var flattened = others.concat(___default.default.flatten(___default.default.pluck(same, "terms"), /* shallow: */true));
2749
3205
  return new type.func(flattened);
2750
3206
  }
3207
+
3208
+ // reduce a numeric sequence to a Num
3209
+
2751
3210
  isPositive() {
2752
- var terms = _.invoke(this.terms, "collect");
2753
- return _.all(_.invoke(terms, "isPositive"));
3211
+ var terms = ___default.default.invoke(this.terms, "collect");
3212
+ return ___default.default.all(___default.default.invoke(terms, "isPositive"));
2754
3213
  }
3214
+
3215
+ // return a new Seq with a given term replaced by a different term
3216
+ // (or array of terms). given term can be passed directly, or by index
3217
+ // if no new term is provided, the old one is simply removed
2755
3218
  replace(oldTerm, newTerm) {
2756
- const index = oldTerm instanceof Expr ? _.indexOf(this.terms, oldTerm) : oldTerm;
3219
+ const index = oldTerm instanceof Expr ? ___default.default.indexOf(this.terms, oldTerm) : oldTerm;
2757
3220
  var newTerms = [];
2758
3221
  if (Array.isArray(newTerm)) {
2759
3222
  newTerms = newTerm;
@@ -2763,35 +3226,39 @@ class Seq extends Expr {
2763
3226
  var terms = this.terms.slice(0, index).concat(newTerms).concat(this.terms.slice(index + 1));
2764
3227
  return new this.func(terms);
2765
3228
  }
3229
+
3230
+ // syntactic sugar for replace()
2766
3231
  remove(term) {
2767
3232
  return this.replace(term);
2768
3233
  }
2769
3234
  getDenominator() {
2770
- return new Mul(_.invoke(this.terms, "getDenominator")).flatten();
3235
+ // TODO(alex): find and return LCM
3236
+ return new Mul(___default.default.invoke(this.terms, "getDenominator")).flatten();
2771
3237
  }
2772
3238
  }
3239
+
3240
+ /* sequence of additive terms */
2773
3241
  class Add extends Seq {
2774
- constructor(...args) {
2775
- super(...args);
2776
- this.identity = NumZero;
2777
- this.func = Add;
2778
- }
2779
- eval(vars = {}, options) {
2780
- return _.reduce(this.terms, (memo, term) => {
3242
+ identity = NumZero;
3243
+ func = Add;
3244
+ eval() {
3245
+ let vars = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
3246
+ let options = arguments.length > 1 ? arguments[1] : undefined;
3247
+ return ___default.default.reduce(this.terms, (memo, term) => {
2781
3248
  return memo + term.eval(vars, options);
2782
3249
  }, 0);
2783
3250
  }
2784
3251
  codegen() {
2785
- return _.map(this.terms, term => {
3252
+ return ___default.default.map(this.terms, term => {
2786
3253
  return "(" + term.codegen() + ")";
2787
3254
  }).join(" + ") || "0";
2788
3255
  }
2789
3256
  print() {
2790
- return _.invoke(this.terms, "print").join("+");
3257
+ return ___default.default.invoke(this.terms, "print").join("+");
2791
3258
  }
2792
3259
  tex() {
2793
3260
  let tex = "";
2794
- _.each(this.terms, term => {
3261
+ ___default.default.each(this.terms, term => {
2795
3262
  if (!tex || term.isSubtract()) {
2796
3263
  tex += term.tex();
2797
3264
  } else {
@@ -2801,9 +3268,9 @@ class Add extends Seq {
2801
3268
  return tex;
2802
3269
  }
2803
3270
  collect(options) {
2804
- var terms = _.invoke(this.terms, "collect", options);
3271
+ var terms = ___default.default.invoke(this.terms, "collect", options);
2805
3272
  var pairs = [];
2806
- _.each(terms, term => {
3273
+ ___default.default.each(terms, term => {
2807
3274
  if (term instanceof Mul) {
2808
3275
  var muls = term.partition();
2809
3276
  pairs.push([muls[1].flatten(), muls[0].reduce(options)]);
@@ -2813,20 +3280,30 @@ class Add extends Seq {
2813
3280
  pairs.push([term, NumOne]);
2814
3281
  }
2815
3282
  });
2816
- var grouped = _.groupBy(pairs, pair => {
3283
+
3284
+ // { (Expr expr).print(): [[Expr expr, Num coefficient]] }
3285
+ var grouped = ___default.default.groupBy(pairs, pair => {
2817
3286
  return pair[0].normalize().print();
2818
3287
  });
2819
- var collected = _.compact(_.map(grouped, pairs => {
3288
+ var collected = ___default.default.compact(___default.default.map(grouped, pairs => {
2820
3289
  var expr = pairs[0][0];
2821
- var sum = new Add(_.zip.apply(_, pairs)[1]);
3290
+ var sum = new Add(___default.default.zip.apply(___default.default, pairs)[1]);
2822
3291
  var coefficient = sum.reduce(options);
2823
3292
  return new Mul(coefficient, expr).collect(options);
2824
3293
  }));
3294
+
3295
+ // TODO(alex): use the Pythagorean identity here
3296
+ // e.g. x*sin^2(y) + x*cos^2(y) -> x
3297
+
2825
3298
  return new Add(collected).flatten();
2826
3299
  }
2827
- factor(options = {
2828
- keepNegative: false
2829
- }) {
3300
+
3301
+ // naively factor out anything that is common to all terms
3302
+ // if options.keepNegative is specified, won't factor out a common -1
3303
+ factor() {
3304
+ let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {
3305
+ keepNegative: false
3306
+ };
2830
3307
  const terms = this.terms.map(term => term.collect());
2831
3308
  let factors;
2832
3309
  if (terms[0] instanceof Mul) {
@@ -2834,8 +3311,8 @@ class Add extends Seq {
2834
3311
  } else {
2835
3312
  factors = [terms[0]];
2836
3313
  }
2837
- _.each(_.rest(this.terms), term => {
2838
- factors = _.map(factors, factor => {
3314
+ ___default.default.each(___default.default.rest(this.terms), term => {
3315
+ factors = ___default.default.map(factors, factor => {
2839
3316
  return term.findGCD(factor);
2840
3317
  });
2841
3318
  });
@@ -2848,7 +3325,9 @@ class Add extends Seq {
2848
3325
  return Mul.createOrAppend(left, right).flatten();
2849
3326
  }
2850
3327
  reduce(options) {
2851
- return _.reduce(this.terms, (memo, term) => {
3328
+ return ___default.default.reduce(this.terms,
3329
+ // @ts-expect-error: Type 'Expr' is not assignable to type 'Num'.
3330
+ (memo, term) => {
2852
3331
  return memo.add(term, options);
2853
3332
  }, this.identity);
2854
3333
  }
@@ -2856,12 +3335,14 @@ class Add extends Seq {
2856
3335
  return false;
2857
3336
  }
2858
3337
  isNegative() {
2859
- var terms = _.invoke(this.terms, "collect");
2860
- return _.all(_.invoke(terms, "isNegative"));
3338
+ var terms = ___default.default.invoke(this.terms, "collect");
3339
+ return ___default.default.all(___default.default.invoke(terms, "isNegative"));
2861
3340
  }
2862
3341
  negate() {
2863
- return new Add(_.invoke(this.terms, "negate"));
3342
+ return new Add(___default.default.invoke(this.terms, "negate"));
2864
3343
  }
3344
+
3345
+ // create a new sequence unless left is already one (returns a copy)
2865
3346
  static createOrAppend(left, right) {
2866
3347
  if (left instanceof Add) {
2867
3348
  return new Add(left.terms.concat(right));
@@ -2870,37 +3351,41 @@ class Add extends Seq {
2870
3351
  }
2871
3352
  }
2872
3353
  }
3354
+
3355
+ /* sequence of multiplicative terms */
2873
3356
  class Mul extends Seq {
2874
- constructor(...args) {
2875
- super(...args);
2876
- this.identity = NumOne;
2877
- this.func = Mul;
2878
- }
2879
- eval(vars = {}, options) {
2880
- return _.reduce(this.terms, (memo, term) => {
3357
+ identity = NumOne;
3358
+ func = Mul;
3359
+ eval() {
3360
+ let vars = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
3361
+ let options = arguments.length > 1 ? arguments[1] : undefined;
3362
+ return ___default.default.reduce(this.terms, (memo, term) => {
2881
3363
  return memo * term.eval(vars, options);
2882
3364
  }, 1);
2883
3365
  }
2884
3366
  codegen() {
2885
- return _.map(this.terms, term => {
3367
+ return ___default.default.map(this.terms, term => {
2886
3368
  return "(" + term.codegen() + ")";
2887
3369
  }).join(" * ") || "0";
2888
3370
  }
2889
3371
  print() {
2890
- return _.map(this.terms, term => {
3372
+ return ___default.default.map(this.terms, term => {
2891
3373
  return term instanceof Add ? "(" + term.print() + ")" : term.print();
2892
3374
  }).join("*");
2893
3375
  }
2894
3376
  getUnits() {
2895
- var tmUnits = _(this.terms).chain().map(term => {
3377
+ var tmUnits = ___default.default(this.terms).chain().map(term => {
2896
3378
  return term.getUnits();
2897
3379
  }).flatten().value();
2898
3380
  tmUnits.sort((a, b) => a.unit.localeCompare(b.unit));
2899
3381
  return tmUnits;
2900
3382
  }
3383
+
3384
+ // since we don't care about commutativity, we can render a Mul any way we choose
3385
+ // so we follow convention: first any negatives, then any numbers, then everything else
2901
3386
  tex() {
2902
3387
  var cdot = " \\cdot ";
2903
- var terms = _.groupBy(this.terms, term => {
3388
+ var terms = ___default.default.groupBy(this.terms, term => {
2904
3389
  if (term.isDivide()) {
2905
3390
  return "inverse";
2906
3391
  } else if (term instanceof Num) {
@@ -2909,11 +3394,19 @@ class Mul extends Seq {
2909
3394
  return "other";
2910
3395
  }
2911
3396
  });
3397
+
3398
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
2912
3399
  var inverses = terms.inverse || [];
3400
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
2913
3401
  var numbers = terms.number || [];
3402
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
2914
3403
  var others = terms.other || [];
2915
3404
  var negatives = "";
2916
3405
  var numerator;
3406
+
3407
+ // check all the numbers to see if there is a rational we can extract,
3408
+ // since we would like 1/2x/y to come out as \frac{1}{2}\frac{x}{y},
3409
+ // and not \frac{1x}{2y}.
2917
3410
  for (var i = 0; i < numbers.length; i++) {
2918
3411
  var isRational = numbers[i] instanceof Rational && !(numbers[i] instanceof Int);
2919
3412
  if (isRational && others.length > 0 && inverses.length > 0) {
@@ -2923,84 +3416,102 @@ class Mul extends Seq {
2923
3416
  return numbers[i].tex() + new Mul(newTerms).tex();
2924
3417
  }
2925
3418
  }
2926
- numbers = _.compact(_.map(numbers, term => {
3419
+ numbers = ___default.default.compact(___default.default.map(numbers, term => {
2927
3420
  var shouldPushDown = !term.hints.fraction || inverses.length > 0;
2928
3421
  if (term instanceof Rational && !(term instanceof Int) && shouldPushDown) {
3422
+ // e.g. 3x/4 -> 3/4*x (internally) -> 3x/4 (rendered)
2929
3423
  inverses.push(new Pow(new Int(term.d), NumDiv));
2930
3424
  var number = new Int(term.n);
2931
3425
  number.hints = term.hints;
2932
- return _.any(term.hints) ? number : null;
3426
+ return ___default.default.any(term.hints) ? number : null;
2933
3427
  } else {
2934
3428
  return term;
2935
3429
  }
2936
3430
  }));
2937
3431
  if (numbers.length === 0 && others.length === 1) {
3432
+ // e.g. (x+y)/z -> \frac{x+y}{z}
2938
3433
  numerator = others[0].tex();
2939
3434
  } else {
2940
3435
  var tex = "";
2941
- _.each(numbers, term => {
3436
+ ___default.default.each(numbers, term => {
2942
3437
  if (term.hints.subtract && term.hints.entered) {
2943
3438
  negatives += "-";
2944
3439
  tex += (tex ? cdot : "") + term.abs().tex();
2945
3440
  } else if (term instanceof Int && term.n === -1 && (term.hints.negate || term.hints.subtract)) {
3441
+ // e.g. -1*-1 -> --1
3442
+ // e.g. -1*x -> -x
2946
3443
  negatives += "-";
2947
3444
  } else {
3445
+ // e.g. 2*3 -> 2(dot)3
2948
3446
  tex += (tex ? cdot : "") + term.tex();
2949
3447
  }
2950
3448
  });
2951
- _.each(others, term => {
3449
+ ___default.default.each(others, term => {
2952
3450
  if (term.needsExplicitMul()) {
3451
+ // e.g. 2*2^3 -> 2(dot)2^3
2953
3452
  tex += (tex ? cdot : "") + term.tex();
2954
3453
  } else if (term instanceof Add) {
3454
+ // e.g. (a+b)*c -> (a+b)c
2955
3455
  tex += "(" + term.tex() + ")";
2956
3456
  } else {
3457
+ // e.g. a*b*c -> abc
2957
3458
  tex += term.tex();
2958
3459
  }
2959
3460
  });
2960
3461
  numerator = tex ? tex : "1";
2961
3462
  }
3463
+
3464
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
2962
3465
  if (!inverses.length) {
2963
3466
  return negatives + numerator;
2964
3467
  } else {
2965
- var denominator = new Mul(_.invoke(inverses, "asDivide")).flatten().tex();
3468
+ var denominator = new Mul(___default.default.invoke(inverses, "asDivide")).flatten().tex();
2966
3469
  return negatives + "\\frac{" + numerator + "}{" + denominator + "}";
2967
3470
  }
2968
3471
  }
2969
3472
  strip() {
2970
- var terms = _.map(this.terms, term => {
3473
+ var terms = ___default.default.map(this.terms, term => {
2971
3474
  return term instanceof Num ? term.abs() : term.strip();
2972
3475
  });
2973
3476
  return new Mul(terms).flatten();
2974
3477
  }
3478
+
3479
+ // expand numerator and denominator separately
2975
3480
  expand() {
2976
- const isInverse = function isInverse(term) {
3481
+ const isInverse = function (term) {
2977
3482
  return term instanceof Pow && term.exp.isNegative();
2978
3483
  };
2979
- const isInverseAdd = function isInverseAdd(term) {
3484
+ const isInverseAdd = function (term) {
2980
3485
  return isInverse(term) && isAdd(term.base);
2981
3486
  };
2982
3487
  const mul = this.recurse("expand").flatten();
2983
3488
  const factors = getFactors(mul);
2984
- const hasAdd = _.any(factors, isAdd);
2985
- const hasInverseAdd = _.any(factors, isInverseAdd);
3489
+ const hasAdd = ___default.default.any(factors, isAdd);
3490
+ const hasInverseAdd = ___default.default.any(factors, isInverseAdd);
2986
3491
  if (!(hasAdd || hasInverseAdd)) {
2987
3492
  return mul;
2988
3493
  }
2989
3494
  let [inverses, normals] = partition(factors, isInverse);
2990
3495
  if (hasAdd) {
2991
3496
  const [adds, others] = partition(normals, isAdd);
2992
- const expanded = _.reduce(adds, function (expanded, add) {
2993
- return _.reduce(expanded, function (temp, array) {
2994
- return temp.concat(_.map(add.terms, term => array.concat(term)));
3497
+
3498
+ // loop over each additive sequence
3499
+ const expanded = ___default.default.reduce(adds, function (expanded, add) {
3500
+ // loop over each expanded array of terms
3501
+ return ___default.default.reduce(expanded, function (temp, array) {
3502
+ // loop over each additive sequence's terms
3503
+ return temp.concat(___default.default.map(add.terms, term => array.concat(term)));
2995
3504
  }, []);
2996
3505
  }, [[]]);
2997
- const muls = _.map(expanded, function (array) {
3506
+
3507
+ // join each fully expanded array of factors with remaining multiplicative factors
3508
+ const muls = ___default.default.map(expanded, function (array) {
2998
3509
  return new Mul(others.concat(array)).flatten();
2999
3510
  });
3000
3511
  normals = [new Add(muls)];
3001
3512
  }
3002
3513
  if (hasInverseAdd) {
3003
- const denominator = new Mul(_.invoke(inverses, "getDenominator")).flatten();
3514
+ const denominator = new Mul(___default.default.invoke(inverses, "getDenominator")).flatten();
3004
3515
  inverses = [new Pow(denominator.expand(), NumDiv)];
3005
3516
  }
3006
3517
  return new Mul(normals.concat(inverses)).flatten();
@@ -3010,10 +3521,16 @@ class Mul extends Seq {
3010
3521
  if (!(factored instanceof Mul)) {
3011
3522
  return factored;
3012
3523
  }
3524
+
3525
+ // Combine any factored out Rationals into one, but don't collect
3013
3526
  var [rationals, others] = partition(factored.terms, term => {
3014
3527
  return term instanceof Rational;
3015
3528
  });
3016
- const ratObj = _.reduce(rationals, (memo, term) => {
3529
+
3530
+ // Could also accomplish this by passing a new option
3531
+ // e.g. return memo.mul(term, {autocollect: false});
3532
+ // TODO(alex): Decide whether this is a good use of options or not
3533
+ const ratObj = ___default.default.reduce(rationals, (memo, term) => {
3017
3534
  return {
3018
3535
  n: memo.n * term.n,
3019
3536
  d: memo.d * term.d
@@ -3028,28 +3545,35 @@ class Mul extends Seq {
3028
3545
  collect(options) {
3029
3546
  var partitioned = this.recurse("collect", options).partition();
3030
3547
  var number = partitioned[0].reduce(options);
3548
+
3549
+ // e.g. 0*x -> 0
3031
3550
  if (number.eval() === 0) {
3032
3551
  return NumZero;
3033
3552
  }
3034
3553
  const other = partitioned[1].flatten();
3554
+
3555
+ // e.g. 2*2 -> 4
3556
+ // e.g. 2*2*x -> 4*x
3035
3557
  if (!(other instanceof Mul)) {
3036
3558
  return new Mul(number, other).flatten();
3037
3559
  }
3038
3560
  const others = other.terms;
3039
3561
  var pairs = [];
3040
- _.each(others, term => {
3562
+ ___default.default.each(others, term => {
3041
3563
  if (term instanceof Pow) {
3042
3564
  pairs.push([term.base, term.exp]);
3043
3565
  } else {
3044
3566
  pairs.push([term, NumOne]);
3045
3567
  }
3046
3568
  });
3047
- var grouped = _.groupBy(pairs, pair => {
3569
+
3570
+ // {(Expr base).print(): [[Expr base, Expr exp]]}
3571
+ var grouped = ___default.default.groupBy(pairs, pair => {
3048
3572
  return pair[0].normalize().print();
3049
3573
  });
3050
- var summed = _.compact(_.map(grouped, pairs => {
3574
+ var summed = ___default.default.compact(___default.default.map(grouped, pairs => {
3051
3575
  var base = pairs[0][0];
3052
- var sum = new Add(_.zip.apply(_, pairs)[1]);
3576
+ var sum = new Add(___default.default.zip.apply(___default.default, pairs)[1]);
3053
3577
  var exp = sum.collect(options);
3054
3578
  if (exp instanceof Num && exp.eval() === 0) {
3055
3579
  return null;
@@ -3057,7 +3581,9 @@ class Mul extends Seq {
3057
3581
  return [base, exp];
3058
3582
  }
3059
3583
  }));
3060
- const groupedPairs = _.groupBy(summed, pair => {
3584
+
3585
+ // XXX `pairs` is shadowed four or five times in this function
3586
+ const groupedPairs = ___default.default.groupBy(summed, pair => {
3061
3587
  if (pair[0] instanceof Trig && pair[0].isBasic()) {
3062
3588
  return "trig";
3063
3589
  } else if (pair[0] instanceof Log) {
@@ -3066,24 +3592,33 @@ class Mul extends Seq {
3066
3592
  return "expr";
3067
3593
  }
3068
3594
  });
3595
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
3069
3596
  let trigs = groupedPairs.trig || [];
3597
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
3070
3598
  let logs = groupedPairs.log || [];
3599
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
3071
3600
  const exprs = groupedPairs.expr || [];
3072
3601
  if (trigs.length > 1) {
3073
- var byArg = _.groupBy(trigs, pair => {
3602
+ // combine sines and cosines into other trig functions
3603
+
3604
+ // {Trig.arg.print(): [[Trig base, Expr exp]]}
3605
+ var byArg = ___default.default.groupBy(trigs, pair => {
3074
3606
  return pair[0].arg.normalize().print();
3075
3607
  });
3076
3608
  trigs = [];
3077
- _.each(byArg, pairs => {
3609
+ ___default.default.each(byArg, pairs => {
3078
3610
  const arg = pairs[0][0].arg;
3611
+
3612
+ // {Trig.type: Expr exp}
3079
3613
  let funcs = {
3080
3614
  sin: NumZero,
3081
3615
  cos: NumZero
3082
3616
  };
3083
- _.each(pairs, pair => {
3617
+ ___default.default.each(pairs, pair => {
3084
3618
  funcs[pair[0].type] = pair[1];
3085
3619
  });
3086
3620
  if (Mul.handleNegative(funcs.sin).collect(options).equals(funcs.cos)) {
3621
+ // e.g. sin^x(y)/cos^x(y) -> tan^x(y)
3087
3622
  if (funcs.cos.isNegative()) {
3088
3623
  funcs = {
3089
3624
  tan: funcs.sin
@@ -3094,18 +3629,30 @@ class Mul extends Seq {
3094
3629
  };
3095
3630
  }
3096
3631
  }
3097
- _.each(funcs, (exp, type) => {
3632
+
3633
+ // TODO(alex): combine even if exponents not a perfect match
3634
+ // TODO(alex): transform 1/sin and 1/cos into csc and sec
3635
+
3636
+ ___default.default.each(funcs, (exp, type) => {
3098
3637
  trigs.push([new Trig(type, arg), exp]);
3099
3638
  });
3100
3639
  });
3101
3640
  }
3102
3641
  if (logs.length > 1) {
3103
- var byBase = _.groupBy(logs, pair => {
3642
+ // combine logs with the same base
3643
+
3644
+ // {Log.base.print(): [[Log base, Expr exp]]}
3645
+ var byBase = ___default.default.groupBy(logs, pair => {
3104
3646
  return pair[0].base.normalize().print();
3105
3647
  });
3106
3648
  logs = [];
3107
- _.each(byBase, pairs => {
3649
+ ___default.default.each(byBase, pairs => {
3650
+ // only combine two logs of the same base, otherwise commutative
3651
+ // differences result in different equally valid output
3652
+ // e.g. ln(x)/ln(z)*ln(y) -> log_z(x)*ln(y)
3653
+ // e.g. ln(x)*ln(y)/ln(z) -> ln(x)*log_z(y)
3108
3654
  if (pairs.length === 2 && Mul.handleNegative(pairs[0][1]).collect(options).equals(pairs[1][1])) {
3655
+ // e.g. ln(x)^y/ln(b)^y -> log_b(x)^y
3109
3656
  if (pairs[0][1].isNegative()) {
3110
3657
  logs.push([new Log(pairs[0][0].power, pairs[1][0].power), pairs[1][1]]);
3111
3658
  } else {
@@ -3115,32 +3662,48 @@ class Mul extends Seq {
3115
3662
  logs = logs.concat(pairs);
3116
3663
  }
3117
3664
  });
3665
+
3666
+ // TODO(alex): combine if all inverses are the same e.g. ln(y)*ln(z)/ln(x)/ln(x)
3118
3667
  }
3119
- var collected = _.map([...trigs, ...logs, ...exprs], pair => {
3668
+ var collected = ___default.default.map([...trigs, ...logs, ...exprs], pair => {
3120
3669
  return new Pow(pair[0], pair[1]).collect(options);
3121
3670
  });
3122
3671
  return new Mul([number].concat(collected)).flatten();
3123
3672
  }
3124
3673
  isSubtract() {
3125
- return _.any(this.terms, term => {
3674
+ return ___default.default.any(this.terms, term => {
3126
3675
  return term instanceof Num && Boolean(term.hints.subtract);
3127
3676
  });
3128
3677
  }
3678
+
3679
+ // factor a single -1 in to the Mul
3680
+ // combine with a Num if all Nums are positive, else add as a term
3129
3681
  factorIn(hint) {
3130
3682
  var partitioned = this.partition();
3683
+ // `partition` splits the terms into two Seqs - one containing
3684
+ // only Nums and the all non-Num nodes.
3131
3685
  var numbers = partitioned[0].terms;
3132
- var fold = numbers.length && _.all(numbers, num => num.n > 0);
3686
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
3687
+ var fold = numbers.length && ___default.default.all(numbers, num => num.n > 0);
3688
+
3689
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
3133
3690
  if (fold) {
3691
+ // e.g. - x*2*3 -> x*-2*3
3134
3692
  var num = numbers[0].negate();
3135
3693
  num.hints = numbers[0].hints;
3136
3694
  return this.replace(numbers[0], num.addHint(hint));
3137
3695
  } else {
3696
+ // e.g. - x*y -> -1*x*y
3697
+ // e.g. - x*-2 -> -1*x*-2
3138
3698
  return new Mul([Num.negativeOne(hint)].concat(this.terms));
3139
3699
  }
3140
3700
  }
3701
+
3702
+ // factor out a single hinted -1 (assume it is the division hint)
3703
+ // TODO(alex): make more general or rename to be more specific
3141
3704
  factorOut() {
3142
3705
  var factored = false;
3143
- var terms = _.compact(_.map(this.terms, term => {
3706
+ var terms = ___default.default.compact(___default.default.map(this.terms, term => {
3144
3707
  if (!factored && term instanceof Num && term.hints.divide) {
3145
3708
  factored = true;
3146
3709
  return term.n !== -1 ? term.negate() : null;
@@ -3155,12 +3718,14 @@ class Mul extends Seq {
3155
3718
  }
3156
3719
  }
3157
3720
  reduce(options) {
3158
- return _.reduce(this.terms, (memo, term) => {
3721
+ return ___default.default.reduce(this.terms,
3722
+ // @ts-expect-error: Type 'Expr' is not assignable to type 'Num'.
3723
+ (memo, term) => {
3159
3724
  return memo.mul(term, options);
3160
3725
  }, this.identity);
3161
3726
  }
3162
3727
  findGCD(factor) {
3163
- return new Mul(_.invoke(this.terms, "findGCD", factor)).flatten();
3728
+ return new Mul(___default.default.invoke(this.terms, "findGCD", factor)).flatten();
3164
3729
  }
3165
3730
  asMul() {
3166
3731
  return this;
@@ -3175,7 +3740,7 @@ class Mul extends Seq {
3175
3740
  }
3176
3741
  isNegative() {
3177
3742
  const terms = getFactors(this.collect()).map(factor => factor.isNegative());
3178
- return _.any(terms);
3743
+ return ___default.default.any(terms);
3179
3744
  }
3180
3745
  fold() {
3181
3746
  return Mul.fold(this);
@@ -3184,28 +3749,42 @@ class Mul extends Seq {
3184
3749
  var isNum = expr => {
3185
3750
  return expr instanceof Num;
3186
3751
  };
3187
- const num = _.find(this.terms, isNum);
3752
+ const num = ___default.default.find(this.terms, isNum);
3188
3753
  if (num) {
3189
3754
  return this.replace(num, num.negate());
3190
3755
  } else {
3191
3756
  return new Mul([NumNeg].concat(this.terms));
3192
3757
  }
3193
3758
  }
3759
+
3760
+ // negative signs should be folded into numbers whenever possible
3761
+ // never fold into a Num that's already negative or a Mul that has a negative Num
3762
+ // an optional hint is kept track of to properly render user input
3763
+ // an empty hint means negation
3194
3764
  static handleNegative(expr, hint) {
3195
3765
  if (expr instanceof Num && expr.n > 0) {
3766
+ // e.g. - 2 -> -2
3196
3767
  var negated = expr.negate();
3768
+ // TODO(alex): rework hint system so that this isn't necessary
3197
3769
  negated.hints = expr.hints;
3198
3770
  return negated.addHint(hint);
3199
3771
  } else if (expr instanceof Mul) {
3772
+ // e.g. - x*2*3 -> x*-2*3
3773
+ // e.g. - x*y -> -1*x*y
3774
+ // e.g. - x*-2 -> -1*x*-2
3200
3775
  return expr.factorIn(hint);
3201
3776
  } else {
3777
+ // e.g. - x -> -1*x
3202
3778
  return new Mul(Num.negativeOne(hint), expr);
3203
3779
  }
3204
3780
  }
3781
+
3782
+ // division can create either a Rational or a Mul
3205
3783
  static handleDivide(left, right) {
3784
+ // dividing by a Mul is the same as repeated division by its terms
3206
3785
  if (right instanceof Mul) {
3207
3786
  var first = Mul.handleDivide(left, right.terms[0]);
3208
- var rest = new Mul(_.rest(right.terms)).flatten();
3787
+ var rest = new Mul(___default.default.rest(right.terms)).flatten();
3209
3788
  return Mul.handleDivide(first, rest);
3210
3789
  }
3211
3790
  var isInt = expr => {
@@ -3214,7 +3793,11 @@ class Mul extends Seq {
3214
3793
  var isRational = expr => {
3215
3794
  return expr instanceof Rational;
3216
3795
  };
3217
- if (isInt(right) && left instanceof Mul && _.any(left.terms, isInt)) {
3796
+
3797
+ // for simplification purposes, fold Ints into Rationals if possible
3798
+ // e.g. 3x / 4 -> 3/4 * x (will still render as 3x/4)
3799
+ if (isInt(right) && left instanceof Mul && ___default.default.any(left.terms, isInt)) {
3800
+ // search from the right
3218
3801
  var reversed = left.terms.slice().reverse();
3219
3802
  var num = reversed.find(isRational);
3220
3803
  if (!isInt(num)) {
@@ -3222,6 +3805,9 @@ class Mul extends Seq {
3222
3805
  }
3223
3806
  var rational = new Rational(num.n, right.n);
3224
3807
  rational.hints = num.hints;
3808
+
3809
+ // in the case of something like 1/3 * 6/8, we want the
3810
+ // 6/8 to be considered a fraction, not just a division
3225
3811
  if (num === reversed[0]) {
3226
3812
  rational = rational.addHint("fraction");
3227
3813
  }
@@ -3236,11 +3822,17 @@ class Mul extends Seq {
3236
3822
  if (b instanceof Int) {
3237
3823
  if (a instanceof Int) {
3238
3824
  if (a.n < 0 && b.n < 0) {
3825
+ // e.g. -2 / -3 -> -1*-2/3
3239
3826
  return [NumNeg, new Rational(a.n, -b.n).addHint("fraction")];
3240
3827
  } else {
3828
+ // e.g. 2 / 3 -> 2/3
3829
+ // e.g. -2 / 3 -> -2/3
3830
+ // e.g. 2 / -3 -> -2/3
3241
3831
  return [new Rational(a.n, b.n).addHint("fraction")];
3242
3832
  }
3243
3833
  } else {
3834
+ // e.g. x / 3 -> x*1/3
3835
+ // e.g. x / -3 -> x*-1/3
3244
3836
  var inverse = new Rational(1, b.eval());
3245
3837
  if (b.eval() < 0) {
3246
3838
  return [a, inverse.addHint("negate")];
@@ -3251,42 +3843,66 @@ class Mul extends Seq {
3251
3843
  } else {
3252
3844
  var pow;
3253
3845
  if (b instanceof Trig && b.exp) {
3846
+ // e.g. sin^2(x) -> sin(x)^2
3254
3847
  var exp = b.exp;
3255
3848
  b.exp = undefined;
3256
3849
  b = new Pow(b, exp);
3257
3850
  }
3258
3851
  if (b instanceof Pow) {
3852
+ // e.g. (x^2) ^ -1 -> x^-2
3853
+ // e.g. (x^y) ^ -1 -> x^(-1*y)
3854
+ // e.g. (x^(yz)) ^ -1 -> x^(-1*y*z)
3259
3855
  pow = new Pow(b.base, Mul.handleNegative(b.exp, "divide"));
3260
3856
  } else {
3857
+ // e.g. x ^ -1 -> x^-1
3261
3858
  pow = new Pow(b, NumDiv);
3262
3859
  }
3263
3860
  if (a instanceof Int && a.n === 1) {
3861
+ // e.g. 1 / x -> x^-1
3264
3862
  return [pow];
3265
3863
  } else {
3864
+ // e.g. 2 / x -> 2*x^-1
3266
3865
  return [a, pow];
3267
3866
  }
3268
3867
  }
3269
3868
  };
3270
3869
  if (left instanceof Mul) {
3271
- var divided = divide(_.last(left.terms), right);
3272
- return new Mul(_.initial(left.terms).concat(divided));
3870
+ // NOTE(kevinb): `terms` should always have at least two
3871
+ // elements so getting the last element is safe to do.
3872
+ var divided = divide(___default.default.last(left.terms), right);
3873
+ return new Mul(___default.default.initial(left.terms).concat(divided));
3273
3874
  } else {
3274
3875
  var divided = divide(left, right);
3275
3876
  return new Mul(divided).flatten();
3276
3877
  }
3277
3878
  }
3879
+
3880
+ // fold negative signs into numbers if possible
3881
+ // negative signs are not the same as multiplying by negative one!
3882
+ // e.g. -x -> -1*x simplified
3883
+ // e.g. -2*x -> -2*x simplified
3884
+ // e.g. -x*2 -> -1*x*2 not simplified -> x*-2 simplified
3885
+ // e.g. -1*x*2 -> -1*x*2 not simplified
3886
+
3887
+ // also fold multiplicative terms into open Trig and Log nodes
3888
+ // e.g. (sin x)*x -> sin(x)*x
3889
+ // e.g. sin(x)*x -> sin(x)*x
3890
+ // e.g. sin(x)*(x) -> sin(x)*x
3891
+ // e.g. sin(x)*sin(y) -> sin(x)*sin(y)
3278
3892
  static fold(expr) {
3279
3893
  if (expr instanceof Mul) {
3280
- var trigLog = _.find(_.initial(expr.terms), term => {
3894
+ // assuming that this will be second to last
3895
+ var trigLog = ___default.default.find(___default.default.initial(expr.terms), term => {
3281
3896
  return (term instanceof Trig || term instanceof Log) && Boolean(term.hints.open);
3282
3897
  });
3283
3898
  if (trigLog) {
3284
- const last = _.last(expr.terms);
3899
+ // expr.terms should always have at least two terms
3900
+ const last = ___default.default.last(expr.terms);
3285
3901
  if (trigLog.hints.parens || last.hints.parens || last.has(Trig) || last.has(Log)) {
3286
3902
  trigLog.hints.open = false;
3287
3903
  } else {
3288
3904
  const newTrigLog = trigLog instanceof Trig ? Trig.create([trigLog.type, trigLog.exp], Mul.createOrAppend(trigLog.arg, last).fold()) : Log.create(trigLog.base, Mul.createOrAppend(trigLog.power, last).fold());
3289
- const index = _.indexOf(expr.terms, trigLog);
3905
+ const index = ___default.default.indexOf(expr.terms, trigLog);
3290
3906
  if (index === 0) {
3291
3907
  return newTrigLog;
3292
3908
  } else {
@@ -3302,21 +3918,32 @@ class Mul extends Seq {
3302
3918
  var neg = num => {
3303
3919
  return num.n === -1 && Boolean(num.hints.negate);
3304
3920
  };
3305
- var posOrNeg = function posOrNeg(num) {
3921
+ var posOrNeg = function (num) {
3306
3922
  return pos(num) || neg(num);
3307
3923
  };
3924
+
3925
+ // @ts-expect-error: Type 'Expr' is not assignable to type 'Num'.
3308
3926
  const posNum = numbers.find(pos);
3927
+ // @ts-expect-error: Type 'Expr' is not assignable to type 'Num'.
3309
3928
  const negNum = numbers.find(neg);
3310
- if (numbers.length > 1 && negNum && posNum && _.every(numbers, posOrNeg)) {
3311
- var firstNeg = _.indexOf(expr.terms, negNum);
3312
- var firstNum = _.indexOf(expr.terms, posNum);
3929
+ if (numbers.length > 1 && negNum && posNum &&
3930
+ // @ts-expect-error: Type 'Expr' is not assignable to type 'Num'.
3931
+ ___default.default.every(numbers, posOrNeg)) {
3932
+ var firstNeg = ___default.default.indexOf(expr.terms, negNum);
3933
+ var firstNum = ___default.default.indexOf(expr.terms, posNum);
3934
+
3935
+ // e.g. -x*2 -> x*-2
3313
3936
  if (firstNeg < firstNum) {
3314
3937
  return expr.replace(firstNum, expr.terms[firstNum].negate()).remove(firstNeg);
3315
3938
  }
3316
3939
  }
3317
3940
  }
3941
+
3942
+ // in all other cases, make no change
3318
3943
  return expr;
3319
3944
  }
3945
+
3946
+ // create a new sequence unless left is already one (returns a copy)
3320
3947
  static createOrAppend(left, right) {
3321
3948
  if (left instanceof Mul) {
3322
3949
  return new Mul(left.terms.concat(right));
@@ -3325,23 +3952,39 @@ class Mul extends Seq {
3325
3952
  }
3326
3953
  }
3327
3954
  }
3955
+
3956
+ /* exponentiation */
3328
3957
  class Pow extends Expr {
3958
+ base;
3959
+ exp;
3329
3960
  constructor(base, exp) {
3330
3961
  super();
3331
- this.base = void 0;
3332
- this.exp = void 0;
3333
- this.func = Pow;
3334
3962
  this.base = base;
3335
3963
  this.exp = exp;
3336
3964
  }
3965
+ func = Pow;
3337
3966
  args() {
3338
3967
  return [this.base, this.exp];
3339
3968
  }
3340
- eval(vars = {}, options) {
3969
+ eval() {
3970
+ let vars = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
3971
+ let options = arguments.length > 1 ? arguments[1] : undefined;
3341
3972
  var evaledBase = this.base.eval(vars, options);
3342
3973
  var evaledExp = this.exp.eval(vars, options);
3974
+
3975
+ // Math.pow unequivocally returns NaN when provided with both a
3976
+ // negative base and a fractional exponent. However, in some cases, we
3977
+ // know that our exponent is actually valid for use with negative
3978
+ // bases (e.g., (-5)^(1/3)).
3979
+ //
3980
+ // Here, we explicitly check for such cases. We really only handle a
3981
+ // limited subset (by requiring that the exponent is rational with an
3982
+ // odd denominator), but it's still useful.
3983
+ // See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/pow
3343
3984
  if (evaledBase < 0) {
3344
3985
  var simplifiedExp = this.exp.simplify();
3986
+
3987
+ // If Float, convert to a Rational to enable the logic below
3345
3988
  if (simplifiedExp instanceof Float) {
3346
3989
  var num = simplifiedExp.n;
3347
3990
  var decimals = (num - +num.toFixed()).toString().length - 2;
@@ -3364,6 +4007,7 @@ class Pow extends Expr {
3364
4007
  return this.base.getUnits().map(unit => {
3365
4008
  return {
3366
4009
  unit: unit.unit,
4010
+ // Exponents in units should always be integers
3367
4011
  pow: unit.pow * this.exp.n
3368
4012
  };
3369
4013
  });
@@ -3380,24 +4024,31 @@ class Pow extends Expr {
3380
4024
  }
3381
4025
  tex() {
3382
4026
  if (this.isDivide()) {
4027
+ // e.g. x ^ -1 w/hint -> 1/x
3383
4028
  return "\\frac{1}{" + this.asDivide().tex() + "}";
3384
4029
  } else if (this.isRoot() && isRational(this.exp)) {
3385
4030
  if (this.exp.n !== 1) {
3386
4031
  throw new Error("Node marked with hint 'root' does not have exponent " + "of form 1/x.");
3387
4032
  }
3388
4033
  if (this.exp.d === 2) {
4034
+ // e.g. x ^ 1/2 w/hint -> sqrt{x}
3389
4035
  return "\\sqrt{" + this.base.tex() + "}";
3390
4036
  } else {
4037
+ // e.g. x ^ 1/y w/hint -> sqrt[y]{x}
3391
4038
  return "\\sqrt[" + this.exp.d + "]{" + this.base.tex() + "}";
3392
4039
  }
3393
4040
  } else if (this.base instanceof Trig && !this.base.isInverse() && this.exp instanceof Num && this.exp.isSimple() && this.exp.eval() >= 0) {
4041
+ // e.g sin(x) ^ 2 -> sin^2(x)
3394
4042
  var split = this.base.texSplit();
3395
4043
  return split[0] + "^{" + this.exp.tex() + "}" + split[1];
3396
4044
  } else {
4045
+ // e.g. x ^ y -> x^y
3397
4046
  var base = this.base.tex();
3398
4047
  if (this.base instanceof Seq || this.base instanceof Pow || this.base instanceof Num && !this.base.isSimple()) {
4048
+ // e.g. a+b ^ c -> (a+b)^c
3399
4049
  base = "(" + base + ")";
3400
4050
  } else if (this.base instanceof Trig || this.base instanceof Log) {
4051
+ // e.g. ln(x) ^ 2 -> [ln(x)]^2
3401
4052
  base = "[" + base + "]";
3402
4053
  }
3403
4054
  return base + "^{" + this.exp.tex() + "}";
@@ -3409,30 +4060,43 @@ class Pow extends Expr {
3409
4060
  expand() {
3410
4061
  var pow = this.recurse("expand");
3411
4062
  if (pow.base instanceof Mul) {
3412
- var terms = _.map(pow.base.terms, term => {
4063
+ // e.g. (ab)^c -> a^c*b^c
4064
+
4065
+ var terms = ___default.default.map(pow.base.terms, term => {
3413
4066
  return new Pow(term, pow.exp);
3414
4067
  });
3415
4068
  return new Mul(terms).expand();
3416
4069
  } else if (pow.base instanceof Add && pow.exp instanceof Int && pow.exp.abs().eval() > 1) {
4070
+ // e.g. (a+b)^2 -> a*a+a*b+a*b+b*b
4071
+ // e.g. (a+b)^-2 -> (a*a+a*b+a*b+b*b)^-1
4072
+
3417
4073
  var positive = pow.exp.eval() > 0;
3418
4074
  var n = pow.exp.abs().eval();
3419
- var signed = function signed(mul) {
4075
+ var signed = function (mul) {
3420
4076
  return positive ? mul : new Pow(mul, NumDiv);
3421
4077
  };
4078
+
4079
+ // compute and cache powers of 2 up to n
3422
4080
  const cache = {
3423
4081
  1: pow.base
3424
4082
  };
3425
4083
  for (var i = 2; i <= n; i *= 2) {
3426
- const _mul = new Mul(cache[i / 2], cache[i / 2]);
3427
- cache[i] = _mul.expand().collect();
4084
+ const mul = new Mul(cache[i / 2], cache[i / 2]);
4085
+ cache[i] = mul.expand().collect();
3428
4086
  }
4087
+
4088
+ // if n is a power of 2, you're done!
3429
4089
  if (n in cache) {
3430
4090
  return signed(cache[n]);
3431
4091
  }
3432
- let indices = _.map(n.toString(2).split(""), function (str, i, list) {
4092
+
4093
+ // otherwise decompose n into powers of 2 ...
4094
+ let indices = ___default.default.map(n.toString(2).split(""), function (str, i, list) {
3433
4095
  return Number(str) * Math.pow(2, list.length - i - 1);
3434
4096
  });
3435
- indices = _.without(indices, 0);
4097
+ indices = ___default.default.without(indices, 0);
4098
+
4099
+ // ... then combine
3436
4100
  const factors = [];
3437
4101
  for (const index of indices) {
3438
4102
  if (index in cache) {
@@ -3442,10 +4106,13 @@ class Pow extends Expr {
3442
4106
  const mul = new Mul(factors).expand().collect();
3443
4107
  return signed(mul);
3444
4108
  } else if (pow.exp instanceof Add) {
3445
- const _terms = _.map(pow.exp.terms, term => {
4109
+ // DEFINITELY want behind super-simplify() flag
4110
+ // e.g. x^(a+b) -> x^a*x^b
4111
+
4112
+ const terms = ___default.default.map(pow.exp.terms, term => {
3446
4113
  return new Pow(pow.base, term).expand();
3447
4114
  });
3448
- return new Mul(_terms).expand();
4115
+ return new Mul(terms).expand();
3449
4116
  } else {
3450
4117
  return pow;
3451
4118
  }
@@ -3453,8 +4120,10 @@ class Pow extends Expr {
3453
4120
  factor() {
3454
4121
  var pow = this.recurse("factor");
3455
4122
  if (pow.base instanceof Mul) {
3456
- var terms = _.map(pow.base.terms, term => {
4123
+ var terms = ___default.default.map(pow.base.terms, term => {
3457
4124
  if (term instanceof Int && pow.exp.equals(NumDiv)) {
4125
+ // Anything that can be a Rational should be a Rational
4126
+ // e.g. 2^(-1) -> 1/2
3458
4127
  return new Rational(1, term.n);
3459
4128
  } else {
3460
4129
  return new Pow(term, pow.exp);
@@ -3467,54 +4136,86 @@ class Pow extends Expr {
3467
4136
  }
3468
4137
  collect(options) {
3469
4138
  if (this.base instanceof Pow) {
4139
+ // collect this first to avoid having to deal with float precision
4140
+ // e.g. sqrt(2)^2 -> 2, not 2.0000000000000004
4141
+ // e.g. (x^y)^z -> x^(yz)
3470
4142
  const base = this.base.base;
3471
4143
  const exp = Mul.createOrAppend(this.base.exp, this.exp);
3472
4144
  return new Pow(base, exp).collect(options);
3473
4145
  }
3474
4146
  const pow = this.recurse("collect", options);
3475
- const isSimilarLog = function isSimilarLog(term) {
4147
+ const isSimilarLog = function (term) {
3476
4148
  return term instanceof Log && term.base.equals(pow.base);
3477
4149
  };
3478
4150
  if (pow.exp instanceof Num && pow.exp.eval() === 0) {
4151
+ // e.g. x^0 -> 1
3479
4152
  return NumOne;
3480
4153
  } else if (pow.exp instanceof Num && pow.exp.eval() === 1) {
4154
+ // e.g. x^1 -> x
3481
4155
  return pow.base;
3482
4156
  } else if (isSimilarLog(pow.exp)) {
4157
+ // e.g. b^(log_b(x)) -> x
3483
4158
  return pow.exp.power;
3484
- } else if (pow.exp instanceof Mul && _.any(pow.exp.terms, isSimilarLog)) {
4159
+ } else if (pow.exp instanceof Mul && ___default.default.any(pow.exp.terms, isSimilarLog)) {
4160
+ // e.g. b^(2*y*log_b(x)) -> x^(2*y)
4161
+ // `log` will always be defined here because of the
4162
+ // `_.any(pow.exp.terms, isSimilarLog)` check above.
3485
4163
  const log = pow.exp.terms.find(isSimilarLog);
3486
4164
  const base = log.power;
3487
4165
  const exp = pow.exp.remove(log).flatten();
3488
4166
  return new Pow(base, exp).collect(options);
3489
4167
  } else if (pow.base instanceof Num && pow.exp instanceof Num) {
4168
+ // TODO(alex): Consider encapsualting this logic (and similar logic
4169
+ // elsewhere) into a separate Decimal class for user-entered floats
3490
4170
  if (options && options.preciseFloats) {
4171
+ // Avoid creating an imprecise float
4172
+ // e.g. 23^1.5 -> 12167^0.5, not ~110.304
4173
+
4174
+ // If you take the root as specified by the denominator and
4175
+ // end up with more digits after the decimal point,
4176
+ // the result is imprecise. This works for rationals as well
4177
+ // as floats, but ideally rationals should be pre-processed
4178
+ // e.g. (1/27)^(1/3) -> 1/3 to avoid most cases.
4179
+ // TODO(alex): Catch such cases and avoid converting to floats.
3491
4180
  const exp = pow.exp.asRational();
3492
4181
  const decimalsInBase = pow.base.getDecimalPlaces();
3493
4182
  const root = new Pow(pow.base, new Rational(1, exp.d));
3494
- const decimalsInRoot = root.collect().getDecimalPlaces();
4183
+ const decimalsInRoot = root.collect()
4184
+ // @ts-expect-error: we assume that `root.collect()` returns
4185
+ // a Num here but tbh I'm not sure how this code isn't causing
4186
+ // an infinite loop.
4187
+ .getDecimalPlaces();
3495
4188
  if (decimalsInRoot > decimalsInBase) {
4189
+ // Collecting over this denominator would result in an
4190
+ // imprecise float, so avoid doing so.
3496
4191
  const newBase = new Pow(pow.base, new Int(exp.n)).collect();
3497
4192
  return new Pow(newBase, new Rational(1, exp.d));
3498
4193
  }
3499
4194
  }
4195
+
4196
+ // e.g. 4^1.5 -> 8
3500
4197
  return pow.base.raiseToThe(pow.exp, options);
3501
4198
  } else {
3502
4199
  return pow;
3503
4200
  }
3504
4201
  }
4202
+
4203
+ // checks whether this Pow represents user-entered division
3505
4204
  isDivide() {
3506
- var isDiv = function isDiv(arg) {
4205
+ var isDiv = function (arg) {
3507
4206
  return arg instanceof Num && Boolean(arg.hints.divide);
3508
4207
  };
3509
- return isDiv(this.exp) || this.exp instanceof Mul && _.any(this.exp.terms, isDiv);
4208
+ return isDiv(this.exp) || this.exp instanceof Mul && ___default.default.any(this.exp.terms, isDiv);
3510
4209
  }
4210
+
4211
+ // assuming this Pow represents user-entered division, returns the denominator
3511
4212
  asDivide() {
3512
4213
  if (this.exp instanceof Num) {
3513
4214
  if (this.exp.eval() === -1) {
3514
4215
  return this.base;
3515
4216
  } else {
3516
4217
  var negated = this.exp.negate();
3517
- negated.hints = _.clone(this.exp.hints);
4218
+ negated.hints = ___default.default.clone(this.exp.hints);
3518
4219
  negated.hints.divide = false;
3519
4220
  return new Pow(this.base, negated);
3520
4221
  }
@@ -3530,6 +4231,9 @@ class Pow extends Expr {
3530
4231
  isSquaredTrig() {
3531
4232
  return this.base instanceof Trig && !this.base.isInverse() && this.exp instanceof Num && this.exp.eval() === 2;
3532
4233
  }
4234
+
4235
+ // extract whatever denominator makes sense, ignoring hints
4236
+ // if negative exponent, will recursively include the base's denominator as well
3533
4237
  getDenominator() {
3534
4238
  if (this.exp instanceof Num && this.exp.eval() === -1) {
3535
4239
  return Mul.createOrAppend(this.base, this.base.getDenominator()).flatten();
@@ -3544,17 +4248,27 @@ class Pow extends Expr {
3544
4248
  }
3545
4249
  findGCD(factor) {
3546
4250
  const [base, exp] = factor instanceof Pow ? [factor.base, factor.exp] : [factor, NumOne];
4251
+
4252
+ // GCD is only relevant if same base
3547
4253
  if (this.base.equals(base)) {
3548
4254
  if (this.exp.equals(exp)) {
4255
+ // exact match
4256
+ // e.g. GCD(x^y^z, x^y^z) -> x^y^z
3549
4257
  return this;
3550
4258
  } else if (this.exp instanceof Num && exp instanceof Num) {
4259
+ // two numerical exponents
4260
+ // e.g. GCD(x^3, x^2) -> x^2
3551
4261
  return new Pow(this.base, Num.min(this.exp, exp)).collect();
3552
4262
  } else if (this.exp instanceof Num || exp instanceof Num) {
4263
+ // one numerical exponent
4264
+ // e.g. GCD(x^2, x^y) -> 1
3553
4265
  return NumOne;
3554
4266
  }
3555
4267
  var expA = this.exp.asMul().partition();
3556
4268
  var expB = exp.asMul().partition();
3557
4269
  if (expA[1].equals(expB[1])) {
4270
+ // exponents match except for coefficient
4271
+ // e.g. GCD(x^3y, x^y) -> x^y
3558
4272
  var coefficient = Num.min(expA[0].reduce(), expB[0].reduce());
3559
4273
  var mul = new Mul(coefficient, expA[1].flatten()).flatten();
3560
4274
  return new Pow(base, mul).collect();
@@ -3577,8 +4291,10 @@ class Pow extends Expr {
3577
4291
  if (exp instanceof Int) {
3578
4292
  var n = exp.eval();
3579
4293
  if (n > 2) {
4294
+ // e.g. x^3 -> x^2
3580
4295
  return new Pow(this.base, new Int(n - 1));
3581
4296
  } else if (n < -2) {
4297
+ // e.g. x^-3 -> x^-2
3582
4298
  return new Pow(this.base, new Int(n + 1));
3583
4299
  }
3584
4300
  }
@@ -3588,28 +4304,40 @@ class Pow extends Expr {
3588
4304
  static sqrt(arg) {
3589
4305
  return new Pow(arg, NumSqrt);
3590
4306
  }
4307
+
4308
+ // NOTE(kevinb): nthroot is used as a constructor so we need to
4309
+ // define it as a static property instead of a static method.
4310
+ // TODO(kevinb): update parser-generator.ts to call nthrooth
4311
+ // without using `new`.
4312
+ static nthroot = function (radicand, degree) {
4313
+ var exp = Mul.fold(Mul.handleDivide(new Int(1), degree));
4314
+
4315
+ // FIXME(johnsullivan): If oneOverDegree ends up being a pow object,
4316
+ // this "root" hint is lost between here and when tex() is called.
4317
+ return new Pow(radicand, exp.addHint("root"));
4318
+ };
3591
4319
  }
3592
- _class5 = Pow;
3593
- Pow.nthroot = function (radicand, degree) {
3594
- var exp = Mul.fold(Mul.handleDivide(new Int(1), degree));
3595
- return new _class5(radicand, exp.addHint("root"));
3596
- };
4320
+
4321
+ /* logarithm */
3597
4322
  class Log extends Expr {
4323
+ base;
4324
+ power;
3598
4325
  constructor(base, power) {
3599
4326
  super();
3600
- this.base = void 0;
3601
- this.power = void 0;
3602
- this.func = Log;
3603
4327
  this.base = base;
3604
4328
  this.power = power;
3605
- this.hints = _extends({}, this.hints, {
4329
+ this.hints = {
4330
+ ...this.hints,
3606
4331
  open: false
3607
- });
4332
+ };
3608
4333
  }
4334
+ func = Log;
3609
4335
  args() {
3610
4336
  return [this.base, this.power];
3611
4337
  }
3612
- eval(vars = {}, options) {
4338
+ eval() {
4339
+ let vars = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
4340
+ let options = arguments.length > 1 ? arguments[1] : undefined;
3613
4341
  return Math.log(this.power.eval(vars, options)) / Math.log(this.base.eval(vars, options));
3614
4342
  }
3615
4343
  codegen() {
@@ -3634,10 +4362,13 @@ class Log extends Expr {
3634
4362
  collect(options) {
3635
4363
  var log = this.recurse("collect", options);
3636
4364
  if (log.power instanceof Num && log.power.eval() === 1) {
4365
+ // e.g. ln(1) -> 0
3637
4366
  return NumZero;
3638
4367
  } else if (log.base.equals(log.power)) {
4368
+ // e.g. log_b(b) -> 1
3639
4369
  return NumOne;
3640
4370
  } else if (log.power instanceof Pow && log.power.base.equals(log.base)) {
4371
+ // e.g. log_b(b^x) -> x
3641
4372
  return log.power.exp;
3642
4373
  } else {
3643
4374
  return log;
@@ -3646,13 +4377,21 @@ class Log extends Expr {
3646
4377
  expand() {
3647
4378
  var log = this.recurse("expand");
3648
4379
  if (log.power instanceof Mul) {
3649
- var terms = _.map(log.power.terms, term => {
4380
+ // might want behind super-simplify() flag
4381
+ // e.g. ln(xy) -> ln(x) + ln(y)
4382
+
4383
+ var terms = ___default.default.map(log.power.terms, term => {
4384
+ // need to expand again in case new log powers are Pows
3650
4385
  return new Log(log.base, term).expand();
3651
4386
  });
3652
4387
  return new Add(terms);
3653
4388
  } else if (log.power instanceof Pow) {
4389
+ // e.g. ln(x^y) -> y*ln(x)
4390
+
3654
4391
  return new Mul(log.power.exp, new Log(log.base, log.power.base).expand()).flatten();
3655
4392
  } else if (!log.isNatural()) {
4393
+ // e.g. log_b(x) -> ln(x)/ln(b)
4394
+
3656
4395
  return Mul.handleDivide(new Log(Const.e, log.power), new Log(Const.e, log.base));
3657
4396
  } else {
3658
4397
  return log;
@@ -3686,172 +4425,178 @@ class Log extends Expr {
3686
4425
  return log;
3687
4426
  }
3688
4427
  }
4428
+ /* trigonometric functions */
3689
4429
  class Trig extends Expr {
3690
- constructor(type, _arg) {
4430
+ type; // TODO(kevinb): Use a union type for this
4431
+ arg;
4432
+ exp;
4433
+ constructor(type, arg) {
3691
4434
  super();
3692
- this.type = void 0;
3693
- this.arg = void 0;
3694
- this.exp = void 0;
3695
- this.func = Trig;
3696
- this.functions = {
3697
- sin: {
3698
- eval: Math.sin,
3699
- codegen: "Math.sin((",
3700
- tex: "\\sin",
3701
- expand: () => this
4435
+ this.type = type;
4436
+ this.arg = arg;
4437
+ this.hints = {
4438
+ ...this.hints,
4439
+ open: false
4440
+ };
4441
+ }
4442
+ func = Trig;
4443
+ args() {
4444
+ return [this.type, this.arg];
4445
+ }
4446
+
4447
+ // TODO(kevinb): Use union type for the function names.
4448
+ functions = {
4449
+ sin: {
4450
+ eval: Math.sin,
4451
+ codegen: "Math.sin((",
4452
+ tex: "\\sin",
4453
+ expand: () => this
4454
+ },
4455
+ cos: {
4456
+ eval: Math.cos,
4457
+ codegen: "Math.cos((",
4458
+ tex: "\\cos",
4459
+ expand: () => this
4460
+ },
4461
+ tan: {
4462
+ eval: Math.tan,
4463
+ codegen: "Math.tan((",
4464
+ tex: "\\tan",
4465
+ expand: () => Mul.handleDivide(Trig.sin(this.arg), Trig.cos(this.arg))
4466
+ },
4467
+ csc: {
4468
+ eval: arg => {
4469
+ return 1 / Math.sin(arg);
3702
4470
  },
3703
- cos: {
3704
- eval: Math.cos,
3705
- codegen: "Math.cos((",
3706
- tex: "\\cos",
3707
- expand: () => this
4471
+ codegen: "(1/Math.sin(",
4472
+ tex: "\\csc",
4473
+ expand: () => Mul.handleDivide(NumOne, Trig.sin(this.arg))
4474
+ },
4475
+ sec: {
4476
+ eval: arg => {
4477
+ return 1 / Math.cos(arg);
3708
4478
  },
3709
- tan: {
3710
- eval: Math.tan,
3711
- codegen: "Math.tan((",
3712
- tex: "\\tan",
3713
- expand: () => Mul.handleDivide(Trig.sin(this.arg), Trig.cos(this.arg))
4479
+ codegen: "(1/Math.cos(",
4480
+ tex: "\\sec",
4481
+ expand: () => Mul.handleDivide(NumOne, Trig.cos(this.arg))
4482
+ },
4483
+ cot: {
4484
+ eval: arg => {
4485
+ return 1 / Math.tan(arg);
3714
4486
  },
3715
- csc: {
3716
- eval: arg => {
3717
- return 1 / Math.sin(arg);
3718
- },
3719
- codegen: "(1/Math.sin(",
3720
- tex: "\\csc",
3721
- expand: () => Mul.handleDivide(NumOne, Trig.sin(this.arg))
4487
+ codegen: "(1/Math.tan(",
4488
+ tex: "\\cot",
4489
+ expand: () => Mul.handleDivide(Trig.cos(this.arg), Trig.sin(this.arg))
4490
+ },
4491
+ arcsin: {
4492
+ eval: Math.asin,
4493
+ codegen: "Math.asin((",
4494
+ tex: "\\arcsin"
4495
+ },
4496
+ arccos: {
4497
+ eval: Math.acos,
4498
+ codegen: "Math.acos((",
4499
+ tex: "\\arccos"
4500
+ },
4501
+ arctan: {
4502
+ eval: Math.atan,
4503
+ codegen: "Math.atan((",
4504
+ tex: "\\arctan"
4505
+ },
4506
+ arccsc: {
4507
+ eval: arg => {
4508
+ return Math.asin(1 / arg);
3722
4509
  },
3723
- sec: {
3724
- eval: arg => {
3725
- return 1 / Math.cos(arg);
3726
- },
3727
- codegen: "(1/Math.cos(",
3728
- tex: "\\sec",
3729
- expand: () => Mul.handleDivide(NumOne, Trig.cos(this.arg))
4510
+ codegen: "Math.asin(1/(",
4511
+ tex: "\\operatorname{arccsc}"
4512
+ },
4513
+ arcsec: {
4514
+ eval: arg => {
4515
+ return Math.acos(1 / arg);
3730
4516
  },
3731
- cot: {
3732
- eval: arg => {
3733
- return 1 / Math.tan(arg);
3734
- },
3735
- codegen: "(1/Math.tan(",
3736
- tex: "\\cot",
3737
- expand: () => Mul.handleDivide(Trig.cos(this.arg), Trig.sin(this.arg))
4517
+ codegen: "Math.acos(1/(",
4518
+ tex: "\\operatorname{arcsec}"
4519
+ },
4520
+ arccot: {
4521
+ eval: arg => {
4522
+ return Math.atan(1 / arg);
3738
4523
  },
3739
- arcsin: {
3740
- eval: Math.asin,
3741
- codegen: "Math.asin((",
3742
- tex: "\\arcsin"
4524
+ codegen: "Math.atan(1/(",
4525
+ tex: "\\operatorname{arccot}"
4526
+ },
4527
+ sinh: {
4528
+ eval: arg => {
4529
+ return (Math.exp(arg) - Math.exp(-arg)) / 2;
3743
4530
  },
3744
- arccos: {
3745
- eval: Math.acos,
3746
- codegen: "Math.acos((",
3747
- tex: "\\arccos"
4531
+ codegen: argStr => {
4532
+ return "((Math.exp(" + argStr + ") - Math.exp(-(" + argStr + "))) / 2)";
3748
4533
  },
3749
- arctan: {
3750
- eval: Math.atan,
3751
- codegen: "Math.atan((",
3752
- tex: "\\arctan"
4534
+ tex: "\\sinh",
4535
+ expand: () => this
4536
+ },
4537
+ cosh: {
4538
+ eval: arg => {
4539
+ return (Math.exp(arg) + Math.exp(-arg)) / 2;
3753
4540
  },
3754
- arccsc: {
3755
- eval: arg => {
3756
- return Math.asin(1 / arg);
3757
- },
3758
- codegen: "Math.asin(1/(",
3759
- tex: "\\operatorname{arccsc}"
4541
+ codegen: argStr => {
4542
+ return "((Math.exp(" + argStr + ") + Math.exp(-(" + argStr + "))) / 2)";
3760
4543
  },
3761
- arcsec: {
3762
- eval: arg => {
3763
- return Math.acos(1 / arg);
3764
- },
3765
- codegen: "Math.acos(1/(",
3766
- tex: "\\operatorname{arcsec}"
4544
+ tex: "\\cosh",
4545
+ expand: () => this
4546
+ },
4547
+ tanh: {
4548
+ eval: arg => {
4549
+ return (Math.exp(arg) - Math.exp(-arg)) / (Math.exp(arg) + Math.exp(-arg));
3767
4550
  },
3768
- arccot: {
3769
- eval: arg => {
3770
- return Math.atan(1 / arg);
3771
- },
3772
- codegen: "Math.atan(1/(",
3773
- tex: "\\operatorname{arccot}"
4551
+ codegen: argStr => {
4552
+ return "(" + "(Math.exp(" + argStr + ") - Math.exp(-(" + argStr + ")))" + " / " + "(Math.exp(" + argStr + ") + Math.exp(-(" + argStr + ")))" + ")";
3774
4553
  },
3775
- sinh: {
3776
- eval: arg => {
3777
- return (Math.exp(arg) - Math.exp(-arg)) / 2;
3778
- },
3779
- codegen: argStr => {
3780
- return "((Math.exp(" + argStr + ") - Math.exp(-(" + argStr + "))) / 2)";
3781
- },
3782
- tex: "\\sinh",
3783
- expand: () => this
4554
+ tex: "\\tanh",
4555
+ expand: () => Mul.handleDivide(Trig.sinh(this.arg), Trig.cosh(this.arg))
4556
+ },
4557
+ csch: {
4558
+ eval: arg => {
4559
+ return 2 / (Math.exp(arg) - Math.exp(-arg));
3784
4560
  },
3785
- cosh: {
3786
- eval: arg => {
3787
- return (Math.exp(arg) + Math.exp(-arg)) / 2;
3788
- },
3789
- codegen: argStr => {
3790
- return "((Math.exp(" + argStr + ") + Math.exp(-(" + argStr + "))) / 2)";
3791
- },
3792
- tex: "\\cosh",
3793
- expand: () => this
4561
+ codegen: argStr => {
4562
+ return "(2 / (Math.exp(" + argStr + ") - Math.exp(-(" + argStr + "))))";
3794
4563
  },
3795
- tanh: {
3796
- eval: arg => {
3797
- return (Math.exp(arg) - Math.exp(-arg)) / (Math.exp(arg) + Math.exp(-arg));
3798
- },
3799
- codegen: argStr => {
3800
- return "(" + "(Math.exp(" + argStr + ") - Math.exp(-(" + argStr + ")))" + " / " + "(Math.exp(" + argStr + ") + Math.exp(-(" + argStr + ")))" + ")";
3801
- },
3802
- tex: "\\tanh",
3803
- expand: () => Mul.handleDivide(Trig.sinh(this.arg), Trig.cosh(this.arg))
4564
+ tex: "\\csch",
4565
+ expand: () => Mul.handleDivide(NumOne, Trig.sinh(this.arg))
4566
+ },
4567
+ sech: {
4568
+ eval: arg => {
4569
+ return 2 / (Math.exp(arg) + Math.exp(-arg));
3804
4570
  },
3805
- csch: {
3806
- eval: arg => {
3807
- return 2 / (Math.exp(arg) - Math.exp(-arg));
3808
- },
3809
- codegen: argStr => {
3810
- return "(2 / (Math.exp(" + argStr + ") - Math.exp(-(" + argStr + "))))";
3811
- },
3812
- tex: "\\csch",
3813
- expand: () => Mul.handleDivide(NumOne, Trig.sinh(this.arg))
4571
+ codegen: argStr => {
4572
+ return "(2 / (Math.exp(" + argStr + ") + Math.exp(-(" + argStr + "))))";
3814
4573
  },
3815
- sech: {
3816
- eval: arg => {
3817
- return 2 / (Math.exp(arg) + Math.exp(-arg));
3818
- },
3819
- codegen: argStr => {
3820
- return "(2 / (Math.exp(" + argStr + ") + Math.exp(-(" + argStr + "))))";
3821
- },
3822
- tex: "\\sech",
3823
- expand: () => Mul.handleDivide(NumOne, Trig.cosh(this.arg))
4574
+ tex: "\\sech",
4575
+ expand: () => Mul.handleDivide(NumOne, Trig.cosh(this.arg))
4576
+ },
4577
+ coth: {
4578
+ eval: arg => {
4579
+ return (Math.exp(arg) + Math.exp(-arg)) / (Math.exp(arg) - Math.exp(-arg));
3824
4580
  },
3825
- coth: {
3826
- eval: arg => {
3827
- return (Math.exp(arg) + Math.exp(-arg)) / (Math.exp(arg) - Math.exp(-arg));
3828
- },
3829
- codegen: argStr => {
3830
- return "(" + "(Math.exp(" + argStr + ") + Math.exp(-(" + argStr + ")))" + " / " + "(Math.exp(" + argStr + ") - Math.exp(-(" + argStr + ")))" + ")";
3831
- },
3832
- tex: "\\coth",
3833
- expand: () => Mul.handleDivide(Trig.cosh(this.arg), Trig.sinh(this.arg))
3834
- }
3835
- };
3836
- this.type = type;
3837
- this.arg = _arg;
3838
- this.hints = _extends({}, this.hints, {
3839
- open: false
3840
- });
3841
- }
3842
- args() {
3843
- return [this.type, this.arg];
3844
- }
4581
+ codegen: argStr => {
4582
+ return "(" + "(Math.exp(" + argStr + ") + Math.exp(-(" + argStr + ")))" + " / " + "(Math.exp(" + argStr + ") - Math.exp(-(" + argStr + ")))" + ")";
4583
+ },
4584
+ tex: "\\coth",
4585
+ expand: () => Mul.handleDivide(Trig.cosh(this.arg), Trig.sinh(this.arg))
4586
+ }
4587
+ };
3845
4588
  isEven() {
3846
- return _.contains(["cos", "sec"], this.type);
4589
+ return ___default.default.contains(["cos", "sec"], this.type);
3847
4590
  }
3848
4591
  isInverse() {
3849
4592
  return this.type.indexOf("arc") === 0;
3850
4593
  }
3851
4594
  isBasic() {
3852
- return _.contains(["sin", "cos"], this.type);
4595
+ return ___default.default.contains(["sin", "cos"], this.type);
3853
4596
  }
3854
- eval(vars = {}, options) {
4597
+ eval() {
4598
+ let vars = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
4599
+ let options = arguments.length > 1 ? arguments[1] : undefined;
3855
4600
  var func = this.functions[this.type].eval;
3856
4601
  var arg = this.arg.eval(vars, options);
3857
4602
  return func(arg);
@@ -3896,14 +4641,18 @@ class Trig extends Expr {
3896
4641
  return this;
3897
4642
  }
3898
4643
  }
4644
+
4645
+ // TODO(alex): does every new node type need to redefine these?
3899
4646
  needsExplicitMul() {
3900
4647
  return false;
3901
4648
  }
3902
4649
  expand() {
3903
4650
  var trig = this.recurse("expand");
3904
4651
  if (!trig.isInverse()) {
4652
+ // e.g. tan(x) -> sin(x)/cos(x)
4653
+ // NOTE(kevinb): All non-inverse trig functions have an expand property.
3905
4654
  var expand = trig.functions[trig.type].expand;
3906
- return _.bind(expand, trig)();
4655
+ return ___default.default.bind(expand, trig)();
3907
4656
  } else {
3908
4657
  return trig;
3909
4658
  }
@@ -3913,8 +4662,10 @@ class Trig extends Expr {
3913
4662
  if (!trig.isInverse() && trig.arg.isNegative()) {
3914
4663
  const arg = trig.arg instanceof Num ? trig.arg.abs() : Mul.handleDivide(trig.arg, NumNeg).collect(options);
3915
4664
  if (trig.isEven()) {
4665
+ // e.g. cos(-x) -> cos(x)
3916
4666
  return new Trig(trig.type, arg);
3917
4667
  } else {
4668
+ // e.g. sin(-x) -> -sin(x)
3918
4669
  return new Mul(NumNeg, new Trig(trig.type, arg));
3919
4670
  }
3920
4671
  } else {
@@ -3925,6 +4676,7 @@ class Trig extends Expr {
3925
4676
  var type = pair[0];
3926
4677
  var exp = pair[1];
3927
4678
  if (exp && exp.equals(NumNeg)) {
4679
+ // e.g. sin^-1(x) -> arcsin(x)
3928
4680
  type = "arc" + type;
3929
4681
  exp = undefined;
3930
4682
  }
@@ -3951,16 +4703,18 @@ class Trig extends Expr {
3951
4703
  }
3952
4704
  }
3953
4705
  class Abs extends Expr {
4706
+ arg;
3954
4707
  constructor(arg) {
3955
4708
  super();
3956
- this.arg = void 0;
3957
- this.func = Abs;
3958
4709
  this.arg = arg;
3959
4710
  }
4711
+ func = Abs;
3960
4712
  args() {
3961
4713
  return [this.arg];
3962
4714
  }
3963
- eval(vars = {}, options) {
4715
+ eval() {
4716
+ let vars = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
4717
+ let options = arguments.length > 1 ? arguments[1] : undefined;
3964
4718
  return Math.abs(this.arg.eval(vars, options));
3965
4719
  }
3966
4720
  codegen() {
@@ -3975,11 +4729,14 @@ class Abs extends Expr {
3975
4729
  collect(options) {
3976
4730
  var abs = this.recurse("collect", options);
3977
4731
  if (abs.arg.isPositive()) {
4732
+ // e.g. |2^x| -> 2^x
3978
4733
  return abs.arg;
3979
4734
  } else if (abs.arg instanceof Num) {
4735
+ // e.g. |-2| -> 2
3980
4736
  return abs.arg.abs();
3981
4737
  } else if (abs.arg instanceof Mul) {
3982
- var terms = _.groupBy(abs.arg.terms, term => {
4738
+ // e.g. |-2*pi*x| -> 2*pi*|x|
4739
+ var terms = ___default.default.groupBy(abs.arg.terms, term => {
3983
4740
  if (term.isPositive()) {
3984
4741
  return "positive";
3985
4742
  } else if (term instanceof Num) {
@@ -3988,7 +4745,9 @@ class Abs extends Expr {
3988
4745
  return "other";
3989
4746
  }
3990
4747
  });
3991
- var positives = terms.positive.concat(_.invoke(terms.number, "abs"));
4748
+ var positives = terms.positive.concat(___default.default.invoke(terms.number, "abs"));
4749
+
4750
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
3992
4751
  if (terms.other.length) {
3993
4752
  positives.push(new Abs(new Mul(terms.other).flatten()));
3994
4753
  }
@@ -3997,10 +4756,13 @@ class Abs extends Expr {
3997
4756
  return abs;
3998
4757
  }
3999
4758
  }
4759
+
4760
+ // this should definitely be behind a super-simplify flag
4000
4761
  expand() {
4001
4762
  var abs = this.recurse("expand");
4002
4763
  if (abs.arg instanceof Mul) {
4003
- var terms = _.map(abs.arg.terms, term => {
4764
+ // e.g. |xyz| -> |x|*|y|*|z|
4765
+ var terms = ___default.default.map(abs.arg.terms, term => {
4004
4766
  return new Abs(term);
4005
4767
  });
4006
4768
  return new Mul(terms);
@@ -4012,25 +4774,19 @@ class Abs extends Expr {
4012
4774
  return true;
4013
4775
  }
4014
4776
  }
4777
+
4778
+ /* equation */
4015
4779
  class Eq extends Expr {
4780
+ left;
4781
+ type; // TODO(kevinb): use an enum for this
4782
+ right;
4016
4783
  constructor(left, type, right) {
4017
4784
  super();
4018
- this.left = void 0;
4019
- this.type = void 0;
4020
- this.right = void 0;
4021
- this.func = Eq;
4022
- this.signs = {
4023
- "=": " = ",
4024
- "<": " < ",
4025
- ">": " > ",
4026
- "<>": " \\ne ",
4027
- "<=": " \\le ",
4028
- ">=": " \\ge "
4029
- };
4030
4785
  this.left = left;
4031
4786
  this.type = type;
4032
4787
  this.right = right;
4033
4788
  }
4789
+ func = Eq;
4034
4790
  args() {
4035
4791
  return [this.left, this.type, this.right];
4036
4792
  }
@@ -4040,24 +4796,41 @@ class Eq extends Expr {
4040
4796
  print() {
4041
4797
  return this.left.print() + this.type + this.right.print();
4042
4798
  }
4799
+ signs = {
4800
+ "=": " = ",
4801
+ "<": " < ",
4802
+ ">": " > ",
4803
+ "<>": " \\ne ",
4804
+ "<=": " \\le ",
4805
+ ">=": " \\ge "
4806
+ };
4043
4807
  tex() {
4044
4808
  return this.left.tex() + this.signs[this.type] + this.right.tex();
4045
4809
  }
4046
4810
  normalize() {
4047
4811
  var eq = this.recurse("normalize");
4048
- if (_.contains([">", ">="], eq.type)) {
4812
+ if (___default.default.contains([">", ">="], eq.type)) {
4813
+ // inequalities should have the smaller side on the left
4049
4814
  return new Eq(eq.right, eq.type.replace(">", "<"), eq.left);
4050
4815
  } else {
4051
4816
  return eq;
4052
4817
  }
4053
4818
  }
4054
- asExpr(unfactored = false) {
4819
+
4820
+ // convert this equation to an expression set to zero
4821
+ // the expression is normalized to a canonical form
4822
+ // e.g. y/2=x/4 -> y/2-x/4(=0) -> 2y-x(=0)
4823
+ // unless unfactored is specified, will then divide through
4824
+ asExpr() {
4825
+ let unfactored = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
4055
4826
  var isZero = expr => {
4056
4827
  return expr instanceof Num && expr.isSimple() && expr.eval() === 0;
4057
4828
  };
4829
+
4830
+ // first convert to a sequence of additive terms
4058
4831
  let terms = [];
4059
4832
  if (this.left instanceof Add) {
4060
- terms = _.clone(this.left.terms);
4833
+ terms = ___default.default.clone(this.left.terms);
4061
4834
  } else if (!isZero(this.left)) {
4062
4835
  terms = [this.left];
4063
4836
  }
@@ -4067,16 +4840,26 @@ class Eq extends Expr {
4067
4840
  terms.push(this.right.negate());
4068
4841
  }
4069
4842
  var isInequality = !this.isEquality();
4070
- terms = _.invoke(terms, "collect", {
4843
+
4844
+ // Collect over each term individually to transform simple expressions
4845
+ // into numbers that might have denominators, taking into account
4846
+ // float precision. We have to be very careful to not introduce any
4847
+ // irrational floats before asExpr() returns, because by definition
4848
+ // they do not have exact denominators...
4849
+ terms = ___default.default.invoke(terms, "collect", {
4071
4850
  preciseFloats: true
4072
4851
  });
4852
+
4853
+ // ...and we multiply through by every denominator.
4073
4854
  for (var i = 0; i < terms.length; i++) {
4074
4855
  var denominator = terms[i].getDenominator();
4856
+
4857
+ // Can't multiply inequalities by non 100% positive factors
4075
4858
  if (isInequality && !denominator.isPositive()) {
4076
4859
  denominator = denominator.asPositiveFactor();
4077
4860
  }
4078
4861
  if (!denominator.equals(NumOne)) {
4079
- terms = _.map(terms, term => {
4862
+ terms = ___default.default.map(terms, term => {
4080
4863
  return Mul.createOrAppend(term, denominator).simplify({
4081
4864
  once: true,
4082
4865
  preciseFloats: true
@@ -4087,6 +4870,11 @@ class Eq extends Expr {
4087
4870
  var add = new Add(terms).flatten();
4088
4871
  return unfactored ? add : this.divideThrough(add);
4089
4872
  }
4873
+
4874
+ // divide through by every common factor in the expression
4875
+ // e.g. 2y-4x(=0) -> y-2x(=0)
4876
+ // TODO(alex): Make it an option to only divide by variables/expressions
4877
+ // guaranteed to be nonzero
4090
4878
  divideThrough(expr) {
4091
4879
  const isInequality = !this.isEquality();
4092
4880
  const simplified = expr.simplify({
@@ -4099,24 +4887,41 @@ class Eq extends Expr {
4099
4887
  return expr;
4100
4888
  }
4101
4889
  const terms = factored.terms;
4890
+
4891
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
4102
4892
  const hasVar = term => !!term.getVars().length;
4103
4893
  const isOne = term => term.equals(NumOne);
4104
4894
  const [adds, others] = partition(terms, isAdd);
4895
+
4896
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
4105
4897
  if (adds.length && this.isEquality()) {
4898
+ // keep only Adds
4899
+ // e.g. 2xy(z+1)(=0) -> z+1(=0)
4106
4900
  return new Mul(adds).flatten();
4107
4901
  }
4108
4902
  let denominator = others;
4903
+
4904
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
4109
4905
  if (!adds.length) {
4110
- denominator = _.reject(denominator, hasVar);
4906
+ // if no Adds, keep all variable terms to preserve meaning
4907
+ // e.g. 42xyz(=0) -> xyz(=0)
4908
+ denominator = ___default.default.reject(denominator, hasVar);
4111
4909
  }
4112
4910
  if (isInequality) {
4113
- denominator = _.invoke(denominator, "asPositiveFactor");
4911
+ // can't divide inequalities by non 100% positive factors
4912
+ // e.g. 42x^2y(z+1)(=0) -> y(z+1)(=0)
4913
+ denominator = ___default.default.invoke(denominator, "asPositiveFactor");
4114
4914
  }
4115
- denominator = _.reject(denominator, isOne);
4116
- denominator = _.map(denominator, term => {
4915
+
4916
+ // don't need to divide by one
4917
+ denominator = ___default.default.reject(denominator, isOne);
4918
+ denominator = ___default.default.map(denominator, term => {
4117
4919
  return new Pow(term, NumDiv);
4118
4920
  });
4119
4921
  const dividedResult = new Mul(terms.concat(denominator)).collect();
4922
+
4923
+ // If the end result is the same as the original factoring,
4924
+ // rollback the factoring and discard all intermediate steps.
4120
4925
  if (dividedResult.equals(factored)) {
4121
4926
  return simplified;
4122
4927
  } else {
@@ -4124,9 +4929,10 @@ class Eq extends Expr {
4124
4929
  }
4125
4930
  }
4126
4931
  isEquality() {
4127
- return _.contains(["=", "<>"], this.type);
4932
+ return ___default.default.contains(["=", "<>"], this.type);
4128
4933
  }
4129
4934
  compare(other) {
4935
+ // expression comparisons are handled by Expr.compare()
4130
4936
  if (!(other instanceof Eq)) {
4131
4937
  return false;
4132
4938
  }
@@ -4135,36 +4941,48 @@ class Eq extends Expr {
4135
4941
  if (eq1.type !== eq2.type) {
4136
4942
  return false;
4137
4943
  }
4138
- var expr1 = eq1.divideThrough(eq1.asExpr(true).collect());
4139
- var expr2 = eq2.divideThrough(eq2.asExpr(true).collect());
4944
+
4945
+ // need to collect to properly factor out common factors
4946
+ // e.g x+2x=6 -> 3x=6 -> 3x-6(=0) -> x-2(=0)
4947
+ var expr1 = eq1.divideThrough(eq1.asExpr(/* unfactored */true).collect());
4948
+ var expr2 = eq2.divideThrough(eq2.asExpr(/* unfactored */true).collect());
4140
4949
  if (eq1.isEquality()) {
4950
+ // equals and not-equals can be subtracted either way
4141
4951
  return expr1.compare(expr2) || expr1.compare(Mul.handleNegative(expr2));
4142
4952
  } else {
4143
4953
  return expr1.compare(expr2);
4144
4954
  }
4145
4955
  }
4956
+
4957
+ // should only be done after compare() returns true to avoid false positives
4146
4958
  sameForm(other) {
4147
4959
  var eq1 = this.normalize();
4148
4960
  var eq2 = other.normalize();
4149
4961
  var same = eq1.left.sameForm(eq2.left) && eq1.right.sameForm(eq2.right);
4150
4962
  if (eq1.isEquality()) {
4963
+ // equals and not-equals can be commutative with respect to the sign
4151
4964
  return same || eq1.left.sameForm(eq2.right) && eq1.right.sameForm(eq2.left);
4152
4965
  } else {
4153
4966
  return same;
4154
4967
  }
4155
4968
  }
4969
+
4970
+ // we don't want to override collect because it would turn y=x into y-x(=0)
4971
+ // instead, we ask if the equation was in that form, would it be simplified?
4156
4972
  isSimplified() {
4157
- var expr = this.asExpr(true);
4973
+ var expr = this.asExpr(/* unfactored */true);
4158
4974
  var simplified = this.divideThrough(expr).simplify();
4159
4975
  return expr.equals(simplified) && this.left.isSimplified() && this.right.isSimplified();
4160
4976
  }
4977
+
4978
+ // Assumptions: Expression is of the form a+bx, and we solve for x
4161
4979
  solveLinearEquationForVariable(variable) {
4162
4980
  var expr = this.asExpr();
4163
4981
  if (!(expr instanceof Add) || expr.terms.length !== 2) {
4164
4982
  throw new Error("Can only handle linear equations of the form " + "a + bx (= 0)");
4165
4983
  }
4166
4984
  var hasVar = term => {
4167
- return term.has(Var) && _.contains(term.getVars(), variable.symbol);
4985
+ return term.has(Var) && ___default.default.contains(term.getVars(), variable.symbol);
4168
4986
  };
4169
4987
  const termHasVar = hasVar(expr.terms[0]);
4170
4988
  const a = termHasVar ? Mul.handleNegative(expr.terms[1]) : Mul.handleNegative(expr.terms[0]);
@@ -4172,6 +4990,8 @@ class Eq extends Expr {
4172
4990
  return Mul.handleDivide(a, b).simplify();
4173
4991
  }
4174
4992
  }
4993
+
4994
+ /* abstract symbol node */
4175
4995
  class Sym extends Expr {
4176
4996
  needsExplicitMul() {
4177
4997
  return false;
@@ -4184,15 +5004,17 @@ class Sym extends Expr {
4184
5004
  }
4185
5005
  }
4186
5006
  }
5007
+
5008
+ /* function variable */
4187
5009
  class Func extends Sym {
5010
+ symbol;
5011
+ arg;
4188
5012
  constructor(symbol, arg) {
4189
5013
  super();
4190
- this.symbol = void 0;
4191
- this.arg = void 0;
4192
- this.func = Func;
4193
5014
  this.symbol = symbol;
4194
5015
  this.arg = arg;
4195
5016
  }
5017
+ func = Func;
4196
5018
  args() {
4197
5019
  return [this.symbol, this.arg];
4198
5020
  }
@@ -4202,16 +5024,19 @@ class Func extends Sym {
4202
5024
  tex() {
4203
5025
  return this.symbol + "(" + this.arg.tex() + ")";
4204
5026
  }
4205
- eval(vars = {}, options) {
5027
+ eval() {
5028
+ let vars = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
5029
+ let options = arguments.length > 1 ? arguments[1] : undefined;
4206
5030
  var arg = this.arg;
4207
5031
  var func = vars[this.symbol];
4208
- var newVars = _.extend(_.clone(vars), {
5032
+ var newVars = ___default.default.extend(___default.default.clone(vars), {
4209
5033
  x: arg.eval(vars, options)
4210
5034
  });
4211
5035
  var parsedFunc = parse(func, options);
4212
5036
  if (parsedFunc.parsed) {
4213
5037
  return parsedFunc.expr.eval(newVars, options);
4214
5038
  }
5039
+ // If parsedFunc isn't actually parsed, return its error
4215
5040
  return parsedFunc;
4216
5041
  }
4217
5042
  codegen() {
@@ -4224,22 +5049,24 @@ class Func extends Sym {
4224
5049
  if (excludeFunc) {
4225
5050
  return this.arg.getVars();
4226
5051
  } else {
4227
- return _.union(this.arg.getVars(), [this.symbol]).sort();
5052
+ return ___default.default.union(this.arg.getVars(), [this.symbol]).sort();
4228
5053
  }
4229
5054
  }
4230
5055
  getConsts() {
4231
5056
  return this.arg.getConsts();
4232
5057
  }
4233
5058
  }
5059
+
5060
+ /* variable */
4234
5061
  class Var extends Sym {
5062
+ symbol;
5063
+ subscript;
4235
5064
  constructor(symbol, subscript) {
4236
5065
  super();
4237
- this.symbol = void 0;
4238
- this.subscript = void 0;
4239
- this.func = Var;
4240
5066
  this.symbol = symbol;
4241
5067
  this.subscript = subscript;
4242
5068
  }
5069
+ func = Var;
4243
5070
  args() {
4244
5071
  return [this.symbol, this.subscript];
4245
5072
  }
@@ -4256,6 +5083,9 @@ class Var extends Sym {
4256
5083
  }
4257
5084
  return this.symbol + sub;
4258
5085
  }
5086
+
5087
+ // Provide a way to easily evalate expressions with the common case,
5088
+ // subscripts that consist of a single number or symbol e.g. x_a or x_42
4259
5089
  prettyPrint() {
4260
5090
  var sub = this.subscript;
4261
5091
  if (sub && (sub instanceof Num || sub instanceof Sym)) {
@@ -4275,7 +5105,12 @@ class Var extends Sym {
4275
5105
  repr() {
4276
5106
  return "Var(" + this.print() + ")";
4277
5107
  }
4278
- eval(vars = {}, options) {
5108
+ eval() {
5109
+ let vars = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
5110
+ // @ts-expect-error: values is Vars are strings, but here
5111
+ // we expect them to be numbers. We should probably store
5112
+ // Var and Func entries separately in the Vars type so that
5113
+ // they can be typed correctly.
4279
5114
  return vars[this.prettyPrint()];
4280
5115
  }
4281
5116
  codegen() {
@@ -4288,25 +5123,28 @@ class Var extends Sym {
4288
5123
  return false;
4289
5124
  }
4290
5125
  }
5126
+
5127
+ /* constant */
4291
5128
  class Const extends Sym {
5129
+ symbol;
4292
5130
  constructor(symbol) {
4293
5131
  super();
4294
- this.symbol = void 0;
4295
- this.func = Const;
4296
5132
  this.symbol = symbol;
4297
5133
  }
5134
+ func = Const;
4298
5135
  args() {
4299
5136
  return [this.symbol];
4300
5137
  }
4301
5138
  recurse() {
4302
5139
  return this;
4303
5140
  }
4304
- eval(vars = {}, options) {
5141
+ eval() {
4305
5142
  if (this.symbol === "pi") {
4306
5143
  return Math.PI;
4307
5144
  } else if (this.symbol === "e") {
4308
5145
  return Math.E;
4309
5146
  } else {
5147
+ // @ts-expect-error: should we throw an error here?
4310
5148
  return undefined;
4311
5149
  }
4312
5150
  }
@@ -4316,6 +5154,7 @@ class Const extends Sym {
4316
5154
  } else if (this.symbol === "e") {
4317
5155
  return "Math.E";
4318
5156
  } else {
5157
+ // @ts-expect-error: should we throw an error here?
4319
5158
  return undefined;
4320
5159
  }
4321
5160
  }
@@ -4328,6 +5167,7 @@ class Const extends Sym {
4328
5167
  } else if (this.symbol === "e") {
4329
5168
  return "e";
4330
5169
  } else {
5170
+ // @ts-expect-error: should we return this.symbol here?
4331
5171
  return undefined;
4332
5172
  }
4333
5173
  }
@@ -4344,22 +5184,25 @@ class Const extends Sym {
4344
5184
  getConsts() {
4345
5185
  return [this.print()];
4346
5186
  }
5187
+ static e = new Const("e");
5188
+ static pi = new Const("pi");
4347
5189
  }
4348
- _class12 = Const;
4349
- Const.e = new _class12("e");
4350
- Const.pi = new _class12("pi");
5190
+
5191
+ /* abstract number node */
4351
5192
  class Num extends Expr {
5193
+ n = 0;
4352
5194
  constructor() {
4353
5195
  super();
4354
- this.n = 0;
4355
- this.hints = _extends({}, this.hints, {
5196
+ // hints for interpreting and rendering user input
5197
+ this.hints = {
5198
+ ...this.hints,
4356
5199
  negate: false,
4357
5200
  subtract: false,
4358
5201
  divide: false,
4359
5202
  root: false,
4360
5203
  fraction: false,
4361
5204
  entered: false
4362
- });
5205
+ };
4363
5206
  }
4364
5207
  repr() {
4365
5208
  return this.print();
@@ -4373,9 +5216,17 @@ class Num extends Expr {
4373
5216
  codegen() {
4374
5217
  return this.print();
4375
5218
  }
5219
+
5220
+ // takes another Num and returns a new Num
5221
+
5222
+ // returns this Num's additive inverse
5223
+
4376
5224
  isSubtract() {
4377
5225
  return Boolean(this.hints.subtract);
4378
5226
  }
5227
+
5228
+ // return the absolute value of the number
5229
+
4379
5230
  needsExplicitMul() {
4380
5231
  return true;
4381
5232
  }
@@ -4388,10 +5239,19 @@ class Num extends Expr {
4388
5239
  asPositiveFactor() {
4389
5240
  return this.isPositive() ? this : this.abs();
4390
5241
  }
5242
+
5243
+ // whether a number is considered simple (one term)
5244
+ // e.g. for reals, ints and floats are simple
5245
+
5246
+ // Based on http://stackoverflow.com/a/10454560/2571482
4391
5247
  getDecimalPlaces() {
4392
5248
  var match = ("" + this.n).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/);
4393
5249
  if (match) {
4394
- return Math.max(0, (match[1] ? match[1].length : 0) - (match[2] ? +match[2] : 0));
5250
+ return Math.max(0,
5251
+ // Number of digits right of decimal point
5252
+ (match[1] ? match[1].length : 0) - (
5253
+ // Adjust for scientific notation
5254
+ match[2] ? +match[2] : 0));
4395
5255
  } else {
4396
5256
  return 0;
4397
5257
  }
@@ -4405,10 +5265,16 @@ class Num extends Expr {
4405
5265
  return NumNeg;
4406
5266
  }
4407
5267
  }
5268
+
5269
+ // find the greatest common denominator
4408
5270
  static findGCD(a, b) {
4409
5271
  var mod;
4410
5272
  a = Math.abs(a);
4411
5273
  b = Math.abs(b);
5274
+
5275
+ // Euclid's method doesn't handle non-integers very well. For now
5276
+ // we just say we can't pull out a common factor. It might be
5277
+ // reasonable to do better than this in the future.
4412
5278
  if (a !== Math.floor(a) || b !== Math.floor(b)) {
4413
5279
  return 1;
4414
5280
  }
@@ -4419,19 +5285,26 @@ class Num extends Expr {
4419
5285
  }
4420
5286
  return a;
4421
5287
  }
4422
- static min(...args) {
4423
- return _.min(args, num => num.eval());
5288
+ static min() {
5289
+ for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
5290
+ args[_key3] = arguments[_key3];
5291
+ }
5292
+ return ___default.default.min(args, num => num.eval());
4424
5293
  }
4425
- static max(...args) {
4426
- return _.max(args, num => num.eval());
5294
+ static max() {
5295
+ for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
5296
+ args[_key4] = arguments[_key4];
5297
+ }
5298
+ return ___default.default.max(args, num => num.eval());
4427
5299
  }
4428
5300
  }
5301
+
5302
+ /* rational number (n: numerator, d: denominator) */
4429
5303
  class Rational extends Num {
5304
+ n;
5305
+ d;
4430
5306
  constructor(numerator, denominator) {
4431
5307
  super();
4432
- this.n = void 0;
4433
- this.d = void 0;
4434
- this.func = Rational;
4435
5308
  var n = numerator;
4436
5309
  var d = denominator;
4437
5310
  if (d < 0) {
@@ -4441,6 +5314,7 @@ class Rational extends Num {
4441
5314
  this.n = n;
4442
5315
  this.d = d;
4443
5316
  }
5317
+ func = Rational;
4444
5318
  args() {
4445
5319
  return [this.n, this.d];
4446
5320
  }
@@ -4485,9 +5359,14 @@ class Rational extends Num {
4485
5359
  return new Rational(Math.abs(this.n), this.d);
4486
5360
  }
4487
5361
  findGCD(factor) {
5362
+ // Attempt to factor out common numerators and denominators to return
5363
+ // a Rational instead of a Float
4488
5364
  if (factor instanceof Rational) {
5365
+ // For more background, see
5366
+ // http://math.stackexchange.com/questions/151081/gcd-of-rationals
4489
5367
  var numerator = Num.findGCD(this.n * factor.d, factor.n * this.d);
4490
5368
  var denominator = this.d * factor.d;
5369
+ // Create the rational, then call .collect() to simplify it
4491
5370
  return new Rational(numerator, denominator).collect();
4492
5371
  } else if (factor instanceof Int) {
4493
5372
  return new Rational(Num.findGCD(this.n, factor.n), this.d);
@@ -4495,6 +5374,8 @@ class Rational extends Num {
4495
5374
  return factor.findGCD(this);
4496
5375
  }
4497
5376
  }
5377
+
5378
+ // for now, assuming that exp is a Num
4498
5379
  raiseToThe(exp) {
4499
5380
  if (exp instanceof Int) {
4500
5381
  var positive = exp.eval() > 0;
@@ -4520,11 +5401,13 @@ class Rational extends Num {
4520
5401
  return this;
4521
5402
  }
4522
5403
  }
5404
+
5405
+ /* integer (n: numerator/number) */
4523
5406
  class Int extends Rational {
4524
5407
  constructor(number) {
4525
5408
  super(number, 1);
4526
- this.func = Int;
4527
5409
  }
5410
+ func = Int;
4528
5411
  args() {
4529
5412
  return [this.n];
4530
5413
  }
@@ -4554,19 +5437,24 @@ class Int extends Rational {
4554
5437
  return new Int(n).addHint("entered");
4555
5438
  }
4556
5439
  }
5440
+
5441
+ /* float (n: number) */
4557
5442
  class Float extends Num {
5443
+ n;
4558
5444
  constructor(number) {
4559
5445
  super();
4560
- this.n = void 0;
4561
- this.func = Float;
4562
5446
  this.n = number;
4563
5447
  }
5448
+ func = Float;
4564
5449
  args() {
4565
5450
  return [this.n];
4566
5451
  }
4567
5452
  eval() {
4568
5453
  return this.n;
4569
5454
  }
5455
+
5456
+ // TODO(alex): when we internationalize number parsing/display
5457
+ // we should make sure to use the appropriate decimal mark here
4570
5458
  print() {
4571
5459
  return this.n.toString();
4572
5460
  }
@@ -4588,6 +5476,8 @@ class Float extends Num {
4588
5476
  }
4589
5477
  }
4590
5478
  collect(options) {
5479
+ // We used to simplify Floats to Ints here whenever possible, but no
5480
+ // longer do so in order to preserve significant figures.
4591
5481
  return this;
4592
5482
  }
4593
5483
  negate() {
@@ -4603,6 +5493,8 @@ class Float extends Num {
4603
5493
  return factor.findGCD(this);
4604
5494
  }
4605
5495
  }
5496
+
5497
+ // for now, assuming that exp is a Num
4606
5498
  raiseToThe(exp, options) {
4607
5499
  if (options && options.preciseFloats && exp instanceof Int && exp.n > 1) {
4608
5500
  return Float.toDecimalPlaces(new Pow(this, exp).eval(), this.getDecimalPlaces() * exp.n);
@@ -4610,6 +5502,8 @@ class Float extends Num {
4610
5502
  return new Float(new Pow(this, exp).eval()).collect();
4611
5503
  }
4612
5504
  }
5505
+
5506
+ // only to be used on non-repeating decimals (e.g. user-provided)
4613
5507
  asRational() {
4614
5508
  var parts = this.n.toString().split(".");
4615
5509
  if (parts.length === 1) {
@@ -4629,6 +5523,9 @@ class Float extends Num {
4629
5523
  static create(n) {
4630
5524
  return new Float(n).addHint("entered");
4631
5525
  }
5526
+
5527
+ // Account for floating point imprecision by explicitly controlling the
5528
+ // number of decimal places in common operations (e.g. +, *, ^)
4632
5529
  static toDecimalPlaces(n, places) {
4633
5530
  return new Float(+n.toFixed(Math.min(places, 20))).collect();
4634
5531
  }
@@ -4640,9 +5537,13 @@ const NumSqrt = new Rational(1, 2).addHint("root");
4640
5537
  const NumZero = new Int(0);
4641
5538
  const NumOne = new Int(1);
4642
5539
  const NumTen = new Int(10);
4643
- var parseError = function parseError(str, hash) {
5540
+ var parseError = function (str, hash) {
5541
+ // return int location of parsing error
4644
5542
  throw new Error(hash.loc.first_column);
4645
5543
  };
5544
+
5545
+ // expose concrete nodes to parser scope
5546
+ // see http://zaach.github.io/jison/docs/#sharing-scope
4646
5547
  parser.yy = {
4647
5548
  Add: Add,
4648
5549
  Mul: Mul,
@@ -4659,22 +5560,28 @@ parser.yy = {
4659
5560
  parseError: parseError,
4660
5561
  constants: ["e"],
4661
5562
  symbolLexer: function (symbol) {
4662
- if (_.contains(parser.yy.constants, symbol)) {
5563
+ if (___default.default.contains(parser.yy.constants, symbol)) {
4663
5564
  return "CONST";
4664
- } else if (_.contains(parser.yy.functions, symbol)) {
5565
+ } else if (___default.default.contains(parser.yy.functions, symbol)) {
4665
5566
  return "FUNC";
4666
5567
  } else {
4667
5568
  return "VAR";
4668
5569
  }
4669
5570
  }
4670
5571
  };
4671
- const parse = function parse(input, options) {
5572
+ const parse = function (input, options) {
4672
5573
  try {
4673
5574
  if (options && options.functions) {
4674
- parser.yy.functions = _.without(options.functions, "i");
5575
+ // reserve the symbol "i" for complex numbers
5576
+ parser.yy.functions = ___default.default.without(options.functions, "i");
4675
5577
  } else {
4676
5578
  parser.yy.functions = [];
4677
5579
  }
5580
+
5581
+ // If ',' is the decimal dividor in your country, replace any ','s
5582
+ // with '.'s.
5583
+ // This isn't perfect, since the output will all still have '.'s.
5584
+ // TODO(jack): Fix the output to have ','s in this case
4678
5585
  if (options && options.decimal_separator) {
4679
5586
  input = input.split(options.decimal_separator).join(".");
4680
5587
  }
@@ -4690,20 +5597,29 @@ const parse = function parse(input, options) {
4690
5597
  };
4691
5598
  }
4692
5599
  };
5600
+
5601
+ /* unit */
4693
5602
  class Unit extends Sym {
5603
+ symbol;
4694
5604
  constructor(symbol) {
4695
5605
  super();
4696
- this.symbol = void 0;
4697
- this.func = Unit;
4698
5606
  this.symbol = symbol;
4699
5607
  }
5608
+ func = Unit;
4700
5609
  args() {
4701
5610
  return [this.symbol];
4702
5611
  }
4703
5612
  recurse() {
4704
5613
  return this;
4705
5614
  }
4706
- eval(vars = {}, options) {
5615
+ eval() {
5616
+ // This is called when comparing units. A unit doesn't affect the
5617
+ // numerical value of its coefficient, so this needs to be 1.
5618
+ //
5619
+ // On the other hand, things must not evaluate to the same thing if
5620
+ // they don't have the same type. I believe that's also true - form is
5621
+ // checked before numerical equivalence. I do not know where, though.
5622
+ // However, there are a couple tests checking this.
4707
5623
  return 1;
4708
5624
  }
4709
5625
  getUnits() {
@@ -4721,26 +5637,42 @@ class Unit extends Sym {
4721
5637
  tex() {
4722
5638
  return this.symbol;
4723
5639
  }
5640
+
5641
+ // Simplify units by replacing prefixes with multiplication
4724
5642
  collect(options) {
4725
- if (_(baseUnits).has(this.symbol)) {
5643
+ if (___default.default(baseUnits).has(this.symbol)) {
4726
5644
  return this;
4727
- } else if (_(derivedUnits).has(this.symbol)) {
5645
+ } else if (___default.default(derivedUnits).has(this.symbol)) {
4728
5646
  return derivedUnits[this.symbol].conversion;
4729
5647
  } else {
4730
5648
  throw new Error("could not understand unit: " + this.symbol);
4731
5649
  }
4732
5650
  }
4733
5651
  }
4734
- var unprefixify = function unprefixify(symbol) {
4735
- if (_(baseUnits).has(symbol) || _(derivedUnits).has(symbol)) {
5652
+
5653
+ // If possible, replace unit prefixes with a multiplication.
5654
+ //
5655
+ // "g" -> Unit("g")
5656
+ // "kg" -> 1000 * Unit("g")
5657
+ var unprefixify = function (symbol) {
5658
+ if (___default.default(baseUnits).has(symbol) || ___default.default(derivedUnits).has(symbol)) {
4736
5659
  return new Unit(symbol);
4737
5660
  }
4738
- var prefix = _(_(siPrefixes).keys()).find(testPrefix => {
5661
+
5662
+ // check for prefix
5663
+ var prefix = ___default.default(___default.default(siPrefixes).keys()).find(testPrefix => {
4739
5664
  return new RegExp("^" + testPrefix).test(symbol);
4740
5665
  });
4741
5666
  if (prefix) {
4742
5667
  var base = symbol.replace(new RegExp("^" + prefix), "");
4743
- if (_(baseUnits).has(base) || derivedUnits[base] && derivedUnits[base].prefixes === hasPrefixes) {
5668
+
5669
+ // It's okay to be here if either:
5670
+ // * `base` is a base unit (the seven units listed in baseUnits)
5671
+ // * `base` is a derived unit which allows prefixes
5672
+ //
5673
+ // Otherwise, we're trying to parse a unit label which is not
5674
+ // allowed (mwk, mBTU, etc).
5675
+ if (___default.default(baseUnits).has(base) || derivedUnits[base] && derivedUnits[base].prefixes === hasPrefixes) {
4744
5676
  return new Mul(siPrefixes[prefix], new Unit(base));
4745
5677
  } else {
4746
5678
  throw new Error(base + " does not allow prefixes");
@@ -4749,18 +5681,36 @@ var unprefixify = function unprefixify(symbol) {
4749
5681
  return new Unit(symbol);
4750
5682
  }
4751
5683
  };
4752
- const unitParse = function unitParse(input) {
5684
+ const unitParse = function (input) {
4753
5685
  try {
4754
5686
  var parseResult = unitParser.parse(input);
5687
+
5688
+ // parseResult looks like:
5689
+ // {
5690
+ // magnitude: "5",
5691
+ // unit: {
5692
+ // num: [
5693
+ // { name: "s", pow: 2 }
5694
+ // ],
5695
+ // denom: [
5696
+ // { name: "kg", pow: 1 }
5697
+ // ]
5698
+ // }
5699
+ // }
5700
+ //
5701
+ // denom is optionally null
5702
+
4755
5703
  const unitArray = [];
4756
- _(parseResult.unit.num).each(unitSpec => {
5704
+ ___default.default(parseResult.unit.num).each(unitSpec => {
4757
5705
  unitArray.push(new Pow(unprefixify(unitSpec.name), new Int(unitSpec.pow)));
4758
5706
  });
4759
- _(parseResult.unit.denom).each(unitSpec => {
5707
+ ___default.default(parseResult.unit.denom).each(unitSpec => {
4760
5708
  unitArray.push(new Pow(unprefixify(unitSpec.name), new Int(-1 * unitSpec.pow)));
4761
5709
  });
4762
5710
  var unit = new Mul(unitArray).flatten();
4763
5711
  if (parseResult.type === "unitMagnitude") {
5712
+ // in the first case we have a magnitude coefficient as well as the
5713
+ // unit itself.
4764
5714
  var coefArray = [new Float(+parseResult.magnitude), ...unitArray];
4765
5715
  var expr = new Mul(coefArray);
4766
5716
  return {
@@ -4771,6 +5721,7 @@ const unitParse = function unitParse(input) {
4771
5721
  type: parseResult.type
4772
5722
  };
4773
5723
  } else {
5724
+ // in the second case it's just the unit with no magnitude.
4774
5725
  return {
4775
5726
  parsed: true,
4776
5727
  unit: unit,
@@ -4786,6 +5737,7 @@ const unitParse = function unitParse(input) {
4786
5737
  };
4787
5738
  var baseUnits = {
4788
5739
  m: new Unit("m"),
5740
+ // Note: kg is the SI base unit but we use g for consistency
4789
5741
  g: new Unit("g"),
4790
5742
  s: new Unit("s"),
4791
5743
  A: new Unit("A"),
@@ -4810,11 +5762,14 @@ var siPrefixes = {
4810
5762
  T: new Pow(new Int(10), new Int(12)),
4811
5763
  P: new Pow(new Int(10), new Int(15)),
4812
5764
  E: new Pow(new Int(10), new Int(18)),
5765
+ // http://en.wikipedia.org/wiki/Metric_prefix#.22Hella.22_prefix_proposal
4813
5766
  hella: new Pow(new Int(10), new Int(27))
4814
5767
  };
5768
+
5769
+ // Use these two values to mark a unit as either SI-prefixable or not.
4815
5770
  const hasPrefixes = "hasPrefixes";
4816
5771
  const hasntPrefixes = "hasntPrefixes";
4817
- const makeAlias = function makeAlias(str, prefixes) {
5772
+ const makeAlias = function (str, prefixes) {
4818
5773
  var splits = str.split("|");
4819
5774
  var coefficientStr = splits[0].trim();
4820
5775
  var unitsStr = splits[1].trim();
@@ -4835,9 +5790,35 @@ const makeAlias = function makeAlias(str, prefixes) {
4835
5790
  prefixes: prefixes
4836
5791
  };
4837
5792
  };
5793
+
5794
+ // This is a mapping of derived units (or different names for a unit) to their
5795
+ // definitions. For example, an inch is defined as 0.0254 m.
5796
+ //
5797
+ // Definitions don't need to be in terms of base units. For example, tsp is
5798
+ // defined in terms of tbsp (which is defined in terms of cup -> gal -> L ->
5799
+ // m^3). However, units must get simpler. I.e. there's no loop checking.
5800
+ //
5801
+ // makeAlias takes two parameters:
5802
+ // * a string specifying the simplification to perform
5803
+ // - a required pipe separates the constant factor from the base units
5804
+ // - the constant factor is parsed by KAS
5805
+ // - the base units are in a simple format which disallows exponents and
5806
+ // requires multiplicands to be space-separated ("m m" rather than "m^2)
5807
+ // with an optional "/" separating numerator and denominator
5808
+ // - prefixes are not allowed to be used in the converted to units
5809
+ // (note that this restriction, the format of the string, and the choice to
5810
+ // use a string in the first place are made out of laziness to minimize
5811
+ // both typing and parsing)
5812
+ // * a boolean specifying whether or not it's acceptable to use SI units
5813
+ //
5814
+ // Where possible, these units are taken from "The International System of
5815
+ // Units (SI)" 8th edition (2006).
4838
5816
  var derivedUnits = {
5817
+ // mass
5818
+ // The atomic mass unit / dalton.
4839
5819
  Da: makeAlias("1.6605388628 x 10^-24 | g", hasPrefixes),
4840
5820
  u: makeAlias("| Da", hasntPrefixes),
5821
+ // length
4841
5822
  meter: makeAlias("| m", hasntPrefixes),
4842
5823
  meters: makeAlias("| m", hasntPrefixes),
4843
5824
  in: makeAlias("254 / 10000 | m", hasntPrefixes),
@@ -4848,32 +5829,43 @@ var derivedUnits = {
4848
5829
  nmi: makeAlias("1852 | m", hasntPrefixes),
4849
5830
  Å: makeAlias("10^-10 | m", hasntPrefixes),
4850
5831
  pc: makeAlias("3.0857 x 10^16 | m", hasntPrefixes),
5832
+ // time
4851
5833
  min: makeAlias("60 | s", hasntPrefixes),
4852
5834
  hr: makeAlias("3600 | s", hasntPrefixes),
4853
5835
  sec: makeAlias("| s", hasntPrefixes),
5836
+ // TODO(joel) make day work
4854
5837
  day: makeAlias("86400 | s", hasntPrefixes),
4855
5838
  wk: makeAlias("604800 | s", hasntPrefixes),
4856
5839
  fortnight: makeAlias("14 | day", hasntPrefixes),
4857
5840
  shake: makeAlias("10^-8 | s", hasntPrefixes),
4858
5841
  olympiad: makeAlias("126200000 | s", hasntPrefixes),
5842
+ // temperature
4859
5843
  "°C": makeAlias("1 | K", hasntPrefixes),
4860
5844
  "°F": makeAlias("5/9 | K", hasntPrefixes),
4861
5845
  "°R": makeAlias("5/9 | K", hasntPrefixes),
5846
+ // electric charge
4862
5847
  e: makeAlias("1.6021765314 x 10^-19 | C", hasntPrefixes),
5848
+ // speed
4863
5849
  c: makeAlias("299792458 | m / s", hasntPrefixes),
4864
5850
  kn: makeAlias("514/1000 | m / s", hasntPrefixes),
4865
5851
  kt: makeAlias("| kn", hasntPrefixes),
4866
5852
  knot: makeAlias("| kn", hasntPrefixes),
5853
+ // energy
4867
5854
  J: makeAlias("| N m", hasPrefixes),
4868
5855
  BTU: makeAlias("1060 | J", hasntPrefixes),
4869
5856
  cal: makeAlias("4184 / 1000 | J", hasPrefixes),
4870
5857
  eV: makeAlias("1.602176514 x 10^-19 | J", hasPrefixes),
4871
5858
  erg: makeAlias("10^−7 | J", hasPrefixes),
5859
+ // power
4872
5860
  W: makeAlias("| J / s", hasPrefixes),
4873
5861
  "H-e": makeAlias("80 | W", hasntPrefixes),
5862
+ // force
4874
5863
  N: makeAlias("1000 | g m / s s", hasPrefixes),
5864
+ // "lb": makeAlias("4448 / 1000 | N", hasntPrefixes),
5865
+ // 4.4482216152605
4875
5866
  lb: makeAlias("4448221615 / 1000000000 | N", hasntPrefixes),
4876
5867
  dyn: makeAlias("10^-5 | N", hasntPrefixes),
5868
+ // pressure
4877
5869
  Pa: makeAlias("1 | N / m m m", hasPrefixes),
4878
5870
  bar: makeAlias("10^5 | Pa", hasPrefixes),
4879
5871
  "㏔": makeAlias("1/1000 | bar", hasntPrefixes),
@@ -4881,6 +5873,7 @@ var derivedUnits = {
4881
5873
  atm: makeAlias("101325 | Pa", hasntPrefixes),
4882
5874
  Torr: makeAlias("1/760 | atm", hasntPrefixes),
4883
5875
  mmHg: makeAlias("| Torr", hasntPrefixes),
5876
+ // area
4884
5877
  ha: makeAlias("10^4 | m m", hasntPrefixes),
4885
5878
  b: makeAlias("10^−28 | m m", hasPrefixes),
4886
5879
  barn: makeAlias("| b", hasPrefixes),
@@ -4888,6 +5881,7 @@ var derivedUnits = {
4888
5881
  skilodge: makeAlias("10^-31 | m m", hasntPrefixes),
4889
5882
  outhouse: makeAlias("10^-34 | m m", hasntPrefixes),
4890
5883
  shed: makeAlias("10^-52 | m m", hasntPrefixes),
5884
+ // volume
4891
5885
  L: makeAlias("1/1000 | m m m", hasPrefixes),
4892
5886
  gal: makeAlias("3785/1000 | L", hasPrefixes),
4893
5887
  cup: makeAlias("1/16 | gal", hasntPrefixes),
@@ -4900,17 +5894,25 @@ var derivedUnits = {
4900
5894
  "fl. oz.": makeAlias("1/8 | cup", hasntPrefixes),
4901
5895
  tbsp: makeAlias("1/16 | cup", hasntPrefixes),
4902
5896
  tsp: makeAlias("1/3 | tbsp", hasntPrefixes),
5897
+ // rotational
5898
+ // "rad":
4903
5899
  rev: makeAlias("2 pi | rad", hasntPrefixes),
4904
5900
  deg: makeAlias("180 pi | rad", hasntPrefixes),
4905
5901
  "°": makeAlias("| deg", hasntPrefixes),
4906
5902
  arcminute: makeAlias("1/60 | deg", hasntPrefixes),
4907
5903
  arcsec: makeAlias("1/3600 | deg", hasntPrefixes),
5904
+ // dimensionless
5905
+ // "B": makeAlias("10 | dB", hasntPrefixes), // XXX danger - logarithmic
5906
+ // "dB"
5907
+ // "nP"
4908
5908
  Hu: makeAlias("1000 | dB", hasPrefixes),
4909
5909
  dozen: makeAlias("12 |", hasntPrefixes),
5910
+ // XXX
4910
5911
  mol: makeAlias("6.0221412927 x 10^23 |", hasPrefixes),
4911
5912
  "%": makeAlias("1/100 |", hasntPrefixes),
4912
5913
  percent: makeAlias("| %", hasntPrefixes),
4913
5914
  ppm: makeAlias("1/1000000 |", hasntPrefixes),
5915
+ // electric / magnetic
4914
5916
  V: makeAlias("1000 | g m m / s s C", hasPrefixes),
4915
5917
  C: makeAlias("| A s", hasPrefixes),
4916
5918
  ampere: makeAlias("| A", hasntPrefixes),
@@ -4920,6 +5922,8 @@ var derivedUnits = {
4920
5922
  H: makeAlias("| ohm s", hasPrefixes),
4921
5923
  T: makeAlias("1000 | g / C s", hasPrefixes),
4922
5924
  Wb: makeAlias("1000 | g m m / C s", hasPrefixes),
5925
+ // photometry
5926
+ // TODO not sure this is right
4923
5927
  lm: makeAlias("pi x 10^4 | cd / m m", hasntPrefixes),
4924
5928
  lx: makeAlias("| lm / m m", hasntPrefixes),
4925
5929
  nit: makeAlias("| cd / m m", hasntPrefixes),
@@ -4933,17 +5937,38 @@ var derivedUnits = {
4933
5937
  sk: makeAlias("10^-7 | lm", hasntPrefixes),
4934
5938
  skot: makeAlias("| sk", hasntPrefixes),
4935
5939
  bril: makeAlias("10^-11 | lm", hasntPrefixes),
5940
+ // other
4936
5941
  Hz: makeAlias("| / s", hasPrefixes)
4937
5942
  };
4938
5943
  const Zero = NumZero;
4939
5944
  const One = NumOne;
4940
5945
 
4941
- const compare = function compare(expr1, expr2, options = {}) {
5946
+ /**
5947
+ * Compares two expressions for equality.
5948
+ *
5949
+ * Assumes that both expressions have already been parsed.
5950
+ */
5951
+ // TODO(alex): be able to pass a random() function to compare()
5952
+ const compare = function (expr1, expr2) {
5953
+ let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
4942
5954
  const defaults = {
4943
5955
  form: false,
4944
5956
  simplify: false
4945
5957
  };
4946
- const optionsWithDefaults = _extends({}, defaults, options);
5958
+
5959
+ /* Options that could be added in the future:
5960
+ * - Allow ratios: e.g. 3/1 and 3 should both be accepted for something
5961
+ * like slope
5962
+ * - Allow student to choose their own variable names
5963
+ */
5964
+ const optionsWithDefaults = {
5965
+ ...defaults,
5966
+ ...options
5967
+ };
5968
+
5969
+ // TODO(CP-1614): Figure out how to make these messages translatable
5970
+
5971
+ // Variable check
4947
5972
  const vars = expr1.sameVars(expr2);
4948
5973
  if (!vars.equal) {
4949
5974
  let message;
@@ -4959,18 +5984,24 @@ const compare = function compare(expr1, expr2, options = {}) {
4959
5984
  message: message
4960
5985
  };
4961
5986
  }
5987
+
5988
+ // Semantic check
4962
5989
  if (!expr1.compare(expr2)) {
4963
5990
  return {
4964
5991
  equal: false,
4965
5992
  message: null
4966
5993
  };
4967
5994
  }
5995
+
5996
+ // Syntactic check
4968
5997
  if (optionsWithDefaults.form && !expr1.sameForm(expr2)) {
4969
5998
  return {
4970
5999
  equal: false,
4971
6000
  message: "Your answer is not in the correct form."
4972
6001
  };
4973
6002
  }
6003
+
6004
+ // Syntactic check
4974
6005
  if (optionsWithDefaults.simplify && !expr1.isSimplified()) {
4975
6006
  return {
4976
6007
  equal: false,
@@ -4983,5 +6014,24 @@ const compare = function compare(expr1, expr2, options = {}) {
4983
6014
  };
4984
6015
  };
4985
6016
 
4986
- export { Abs, Add, Const, Eq, Float, Func, Int, Log, Mul, One, Pow, Rational, Trig, Unit, Var, Zero, compare, libVersion, parse, unitParse };
6017
+ exports.Abs = Abs;
6018
+ exports.Add = Add;
6019
+ exports.Const = Const;
6020
+ exports.Eq = Eq;
6021
+ exports.Float = Float;
6022
+ exports.Func = Func;
6023
+ exports.Int = Int;
6024
+ exports.Log = Log;
6025
+ exports.Mul = Mul;
6026
+ exports.One = One;
6027
+ exports.Pow = Pow;
6028
+ exports.Rational = Rational;
6029
+ exports.Trig = Trig;
6030
+ exports.Unit = Unit;
6031
+ exports.Var = Var;
6032
+ exports.Zero = Zero;
6033
+ exports.compare = compare;
6034
+ exports.libVersion = libVersion;
6035
+ exports.parse = parse;
6036
+ exports.unitParse = unitParse;
4987
6037
  //# sourceMappingURL=index.js.map