@trebco/treb 31.9.1 → 32.3.1

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.
Files changed (35) hide show
  1. package/dist/treb-spreadsheet.mjs +15 -15
  2. package/i18n/languages/treb-i18n-da.mjs +1 -0
  3. package/i18n/languages/treb-i18n-de.mjs +1 -0
  4. package/i18n/languages/treb-i18n-es.mjs +1 -0
  5. package/i18n/languages/treb-i18n-fr.mjs +1 -0
  6. package/i18n/languages/treb-i18n-it.mjs +1 -0
  7. package/i18n/languages/treb-i18n-nl.mjs +1 -0
  8. package/i18n/languages/treb-i18n-no.mjs +1 -0
  9. package/i18n/languages/treb-i18n-pl.mjs +1 -0
  10. package/i18n/languages/treb-i18n-pt.mjs +1 -0
  11. package/i18n/languages/treb-i18n-sv.mjs +1 -0
  12. package/package.json +1 -1
  13. package/treb-base-types/src/style.ts +20 -0
  14. package/treb-base-types/src/union.ts +6 -0
  15. package/treb-base-types/src/value-type.ts +13 -0
  16. package/treb-calculator/src/calculator.ts +20 -1
  17. package/treb-calculator/src/descriptors.ts +49 -4
  18. package/treb-calculator/src/expression-calculator.ts +263 -12
  19. package/treb-calculator/src/functions/base-functions.ts +49 -0
  20. package/treb-calculator/src/functions/fp.ts +339 -0
  21. package/treb-calculator/src/functions/gamma.ts +143 -0
  22. package/treb-calculator/src/functions/lambda-functions.ts +96 -0
  23. package/treb-calculator/src/functions/statistics-functions.ts +88 -0
  24. package/treb-data-model/src/data_model.ts +28 -13
  25. package/treb-data-model/src/named.ts +7 -0
  26. package/treb-data-model/src/sheet.ts +19 -2
  27. package/treb-embed/src/embedded-spreadsheet.ts +22 -5
  28. package/treb-embed/style/theme-defaults.scss +0 -22
  29. package/treb-grid/src/editors/editor.ts +14 -0
  30. package/treb-grid/src/types/grid.ts +74 -28
  31. package/treb-parser/src/parser-types.ts +24 -0
  32. package/treb-parser/src/parser.ts +157 -223
  33. package/dist/treb-export-worker.mjs +0 -2
  34. package/dist/treb.d.ts +0 -2235
  35. package/treb-calculator/tsconfig.json +0 -7
@@ -411,6 +411,13 @@ export class Parser {
411
411
  }
412
412
  break;
413
413
 
414
+ case 'implicit-call':
415
+ if (func(unit)) {
416
+ unit.call = this.Walk2(unit.call, func);
417
+ unit.args = unit.args.map(source => this.Walk2(source, func));
418
+ }
419
+ break;
420
+
414
421
  case 'call':
415
422
  if (func(unit)) {
416
423
  unit.args = unit.args.map(source => this.Walk2(source, func));
@@ -477,6 +484,15 @@ export class Parser {
477
484
  }
478
485
  return;
479
486
 
487
+ case 'implicit-call':
488
+ if (func(unit)) {
489
+ this.Walk(unit.call, func);
490
+ for (const arg of unit.args) {
491
+ this.Walk(arg, func);
492
+ }
493
+ }
494
+ return;
495
+
480
496
  case 'call':
481
497
  if (func(unit)) {
482
498
  for (const arg of unit.args) {
@@ -586,14 +602,15 @@ export class Parser {
586
602
  if (options.pass_through_addresses) {
587
603
  return unit.label;
588
604
  }
589
- return options.r1c1 ? this.R1C1Label(unit, options.r1c1_base) : this.AddressLabel(unit, offset);
605
+ return options.r1c1 ? this.R1C1Label(unit, options.r1c1_base, options.r1c1_force_relative) : this.AddressLabel(unit, offset);
590
606
 
591
607
  case 'range':
592
608
  if (options.pass_through_addresses) {
593
609
  return unit.label;
594
610
  }
595
611
  return options.r1c1 ?
596
- this.R1C1Label(unit.start, options.r1c1_base) + ':' + this.R1C1Label(unit.end, options.r1c1_base) :
612
+ this.R1C1Label(unit.start, options.r1c1_base, options.r1c1_force_relative) + ':' +
613
+ this.R1C1Label(unit.end, options.r1c1_base, options.r1c1_force_relative) :
597
614
  this.AddressLabel(unit.start, offset) + ':' + this.AddressLabel(unit.end, offset);
598
615
 
599
616
  case 'missing':
@@ -755,6 +772,10 @@ export class Parser {
755
772
  .map((x) => this.Render(x, options)).join(separator);
756
773
  }
757
774
 
775
+ case 'implicit-call':
776
+ return this.Render(unit.call, options) +
777
+ '(' + unit.args.map(element => this.Render(element, options)).join(separator) + ')';
778
+
758
779
  case 'call':
759
780
  return (
760
781
  unit.name +
@@ -935,10 +956,14 @@ export class Parser {
935
956
 
936
957
  /**
937
958
  * generates absolute or relative R1C1 address
959
+ *
960
+ * FIXME: not supporting relative (offset) addresses atm? I'd like to
961
+ * change this but I don't want to break anything...
938
962
  */
939
963
  protected R1C1Label(
940
964
  address: UnitAddress,
941
965
  base?: UnitAddress,
966
+ force_relative = false,
942
967
  ): string {
943
968
 
944
969
  let label = '';
@@ -948,8 +973,30 @@ export class Parser {
948
973
  '\'' + address.sheet + '\'' : address.sheet) + '!';
949
974
  }
950
975
 
976
+ let row = '';
977
+ let column = '';
978
+
979
+ if (force_relative && base) {
980
+ const delta_row = address.row - base.row;
981
+ const delta_column = address.column - base.column;
982
+
983
+ if (delta_row) {
984
+ row = `[${delta_row}]`;
985
+ }
986
+ if (delta_column) {
987
+ column = `[${delta_column}]`;
988
+ }
989
+
990
+ }
991
+ else {
992
+ row = address.offset_row ? `[${address.row}]` : (address.row + 1).toString();
993
+ column = address.offset_column ? `[${address.column}]` : (address.column + 1).toString();
994
+ }
995
+
996
+ /*
951
997
  const row = (address.absolute_row || !base) ? (address.row + 1).toString() : `[${address.row - base.row}]`;
952
998
  const column = (address.absolute_column || !base) ? (address.column + 1).toString() : `[${address.column - base.column}]`;
999
+ */
953
1000
 
954
1001
  label += `R${row}C${column}`;
955
1002
 
@@ -1009,11 +1056,12 @@ export class Parser {
1009
1056
  *
1010
1057
  * @param exit exit on specific characters
1011
1058
  */
1012
- protected ParseGeneric(exit: number[] = [0]): ExpressionUnit | null {
1059
+ protected ParseGeneric(exit: number[] = [0], explicit_group = false): ExpressionUnit | null {
1013
1060
  let stream: ExpressionUnit[] = [];
1014
1061
 
1015
1062
  for (; this.index < this.length;) {
1016
1063
  const unit = this.ParseNext(stream.length === 0);
1064
+
1017
1065
  if (typeof unit === 'number') {
1018
1066
 
1019
1067
  if (exit.some((test) => unit === test)) {
@@ -1025,8 +1073,11 @@ export class Parser {
1025
1073
  // so we only have to worry about grouping. parse
1026
1074
  // up to the closing paren...
1027
1075
 
1076
+ // actually now we have implicit calls, so we need
1077
+ // to manage that here.
1078
+
1028
1079
  this.index++; // open paren
1029
- const group = this.ParseGeneric([CLOSE_PAREN]);
1080
+ const group = this.ParseGeneric([CLOSE_PAREN], true);
1030
1081
  this.index++; // close paren
1031
1082
 
1032
1083
  // and wrap up in a group element to prevent reordering.
@@ -1034,14 +1085,18 @@ export class Parser {
1034
1085
 
1035
1086
  // skip nulls
1036
1087
 
1037
- if (group) {
1088
+ // ...don't skip nulls? don't know what the rationale was
1089
+ // but for implicit calls we will need to support empty arguments
1090
+
1091
+ // if (group) {
1038
1092
  stream.push({
1039
1093
  type: 'group',
1040
1094
  id: this.id_counter++,
1041
- elements: [group],
1095
+ elements: group? [group] : [],
1042
1096
  explicit: true,
1043
1097
  });
1044
- }
1098
+ //}
1099
+
1045
1100
  }
1046
1101
  else {
1047
1102
  // this can probably move to PNext? except for the test
@@ -1051,6 +1106,21 @@ export class Parser {
1051
1106
  if (operator) {
1052
1107
  stream.push(operator);
1053
1108
  }
1109
+ else if (explicit_group && unit === this.argument_separator_char) {
1110
+
1111
+ // adding a new unit type here to explicitly show we're in
1112
+ // a group; prevents later passes from treating arguments as
1113
+ // fractions or something else. we just need to remove these
1114
+ // later
1115
+
1116
+ stream.push({
1117
+ type: 'group-separator',
1118
+ position: this.index,
1119
+ id: this.id_counter++,
1120
+ });
1121
+
1122
+ this.index++;
1123
+ }
1054
1124
  else {
1055
1125
  this.error = `unexpected character [1]: ${String.fromCharCode(unit)}, 0x${unit.toString(16)}`;
1056
1126
  this.valid = false;
@@ -1231,13 +1301,6 @@ export class Parser {
1231
1301
  // fix ordering of binary operations based on precedence; also
1232
1302
  // convert and validate ranges
1233
1303
 
1234
- // return this.BinaryToRange(this.ArrangeUnits(stream));
1235
- // return this.ArrangeUnits(stream);
1236
-
1237
- // const arranged = this.ArrangeUnits(stream);
1238
- // const result = this.BinaryToComplex(arranged);
1239
- // return result;
1240
-
1241
1304
  return this.BinaryToComplex(this.ArrangeUnits(stream));
1242
1305
 
1243
1306
  }
@@ -1612,206 +1675,13 @@ export class Parser {
1612
1675
 
1613
1676
 
1614
1677
  /**
1615
- * converts binary operations with a colon operator to ranges. this also
1616
- * validates that there are no colon operations with non-address operands
1617
- * (which is why it's called after precendence reordering; colon has the
1618
- * highest preference). recursive only over binary ops AND unary ops.
1619
- *
1620
- * NOTE: there are other legal arguments to a colon operator. specifically:
1621
- *
1622
- * (1) two numbers, in either order
1623
- *
1624
- * 15:16
1625
- * 16:16
1626
- * 16:15
1627
- *
1628
- * (2) with one or both optionally having a $
1629
- *
1630
- * 15:$16
1631
- * $16:$16
1632
- *
1633
- * (3) two column identifiers, in either order
1634
- *
1635
- * A:F
1636
- * B:A
1637
- *
1638
- * (4) and the same with $
1639
- *
1640
- * $A:F
1641
- * $A:$F
1678
+ * reorders operations for precendence
1642
1679
  *
1643
- * because none of these are legal in any other context, we leave the
1644
- * default treatment of them UNLESS they are arguments to the colon
1645
- * operator, in which case we will grab them. that does mean we parse
1646
- * them twice, but (...)
1680
+ * this method was written with the assumption that groups were
1681
+ * always an error. that's no longer true, with implicit calls.
1682
+ * we should still error if it's not an _explicit_ group, i.e. there's
1683
+ * just a bunch of naked tokens.
1647
1684
  *
1648
- * FIXME: will need some updated to rendering these, we don't have any
1649
- * handler for rendering infinity
1650
- */
1651
- protected BinaryToRangeX(unit: ExpressionUnit): ExpressionUnit {
1652
- if (unit.type === 'binary') {
1653
- if (unit.operator === ':') {
1654
-
1655
- let range: UnitRange|undefined;
1656
- let label = '';
1657
-
1658
- if (unit.left.type === 'address' && unit.right.type === 'address') {
1659
- // construct a label using the full text. there's a possibility,
1660
- // I suppose, that there are spaces (this should probably not be
1661
- // legal). this is a canonical label, though (generated)
1662
-
1663
- // it might be better to let this slip, or treat it as an error
1664
- // and force a correction... not sure (TODO/FIXME)
1665
-
1666
- const start_index = unit.left.position + unit.left.label.length;
1667
- const end_index = unit.right.position;
1668
-
1669
- range = {
1670
- type: 'range',
1671
- id: this.id_counter++,
1672
- position: unit.left.position,
1673
- start: unit.left,
1674
- end: unit.right,
1675
- label:
1676
- unit.left.label +
1677
- this.expression.substring(start_index, end_index) +
1678
- unit.right.label,
1679
- };
1680
-
1681
- label = range.start.label + ':' + range.end.label;
1682
-
1683
- this.address_refcount[range.start.label]--;
1684
- this.address_refcount[range.end.label]--;
1685
-
1686
- // remove entries from the list for start, stop
1687
- const positions = [unit.left.position, unit.right.position];
1688
- this.full_reference_list = this.full_reference_list.filter((test) => {
1689
- return (
1690
- test.position !== positions[0] && test.position !== positions[1]
1691
- );
1692
- });
1693
-
1694
- }
1695
- else if ((unit.left.type === 'literal' || unit.left.type === 'identifier')
1696
- && (unit.right.type === 'literal' || unit.right.type === 'identifier')) {
1697
-
1698
- // see if we can plausibly interpret both of these as rows or columns
1699
-
1700
- const left = this.UnitToAddress(unit.left);
1701
- const right = this.UnitToAddress(unit.right);
1702
-
1703
- // and they need to match
1704
-
1705
- if (left && right
1706
- && ((left.column === Infinity && right.column === Infinity)
1707
- || (left.row === Infinity && right.row === Infinity))) {
1708
-
1709
- label = left.label + ':' + right.label;
1710
-
1711
- // we don't support out-of-order ranges, so we should correct.
1712
- // they just won't work otherwise. (TODO/FIXME)
1713
-
1714
- range = {
1715
- type: 'range',
1716
- id: this.id_counter++,
1717
- position: unit.left.position,
1718
- start: left,
1719
- end: right,
1720
- label,
1721
- };
1722
-
1723
- }
1724
-
1725
- }
1726
-
1727
- /*
1728
- else if ( unit.left.type === 'literal'
1729
- && unit.right.type === 'literal'
1730
- && typeof unit.left.value === 'number'
1731
- && typeof unit.right.value === 'number') {
1732
-
1733
- // technically we don't want to support any number that has
1734
- // a decimal place, but I'm not sure we have a useful way of
1735
- // measuring that... could look at the original text?
1736
-
1737
- if (unit.left.value > 0
1738
- && unit.right.value > 0
1739
- && !/\./.test(unit.left.text||'')
1740
- && !/\./.test(unit.right.text||'')
1741
- ) {
1742
-
1743
- label = unit.left.value.toString() + ':' + unit.right.value.toString();
1744
-
1745
- console.info('m2:', label);
1746
-
1747
- const left: UnitAddress = {
1748
- type: 'address',
1749
- position: unit.left.position,
1750
- label: unit.left.value.toString(),
1751
- row: unit.left.value - 1,
1752
- id: this.id_counter++,
1753
- column: Infinity,
1754
- };
1755
-
1756
- const right: UnitAddress = {
1757
- type: 'address',
1758
- position: unit.right.position,
1759
- label: unit.right.value.toString(),
1760
- row: unit.right.value - 1,
1761
- id: this.id_counter++,
1762
- column: Infinity,
1763
- };
1764
-
1765
- range = {
1766
- type: 'range',
1767
- id: this.id_counter++,
1768
- position: unit.left.position,
1769
- start: left,
1770
- end: right,
1771
- label,
1772
- };
1773
-
1774
- }
1775
-
1776
- }
1777
- */
1778
-
1779
- if (range) {
1780
-
1781
- this.dependencies.ranges[label] = range;
1782
-
1783
- // and add the range
1784
- this.full_reference_list.push(range);
1785
-
1786
- return range;
1787
-
1788
- }
1789
- else {
1790
- this.error = `unexpected character: :`;
1791
- this.valid = false;
1792
- // console.info('xx', unit);
1793
- }
1794
-
1795
- }
1796
-
1797
- // recurse
1798
-
1799
- unit.left = this.BinaryToRangeX(unit.left);
1800
- unit.right = this.BinaryToRangeX(unit.right);
1801
- }
1802
-
1803
- // this should no longer be required, because we explicitly check
1804
- // when we construct the unary operations...
1805
-
1806
- // else if (unit.type === 'unary') {
1807
- // unit.operand = this.BinaryToRange(unit.operand);
1808
- // }
1809
-
1810
- return unit;
1811
- }
1812
-
1813
- /**
1814
- * reorders operations for precendence
1815
1685
  */
1816
1686
  protected ArrangeUnits(stream: ExpressionUnit[]): ExpressionUnit {
1817
1687
 
@@ -1838,6 +1708,10 @@ export class Parser {
1838
1708
  for (let index = 0; index < stream.length; index++) {
1839
1709
  let element = stream[index];
1840
1710
 
1711
+ if (element.type === 'group-separator') {
1712
+ continue; // drop
1713
+ }
1714
+
1841
1715
  // given that we need to support unary operators, the logic needs
1842
1716
  // to be a little different. operators are OK at any position, provided
1843
1717
  // we can construct either a unary or binary operation.
@@ -1848,14 +1722,7 @@ export class Parser {
1848
1722
  // in this case we do it with recursion.
1849
1723
 
1850
1724
  if (unary_operators[element.operator]) {
1851
-
1852
- // MARK X
1853
-
1854
- // const right = this.BinaryToRange(
1855
- // this.ArrangeUnits(stream.slice(index + 1)),
1856
- //);
1857
-
1858
- // const right = this.ArrangeUnits(stream.slice(index + 1));
1725
+
1859
1726
  const right = this.BinaryToComplex(this.ArrangeUnits(stream.slice(index + 1)));
1860
1727
 
1861
1728
  // this ensures we return the highest-level group, even if we recurse
@@ -1932,7 +1799,35 @@ export class Parser {
1932
1799
  if (stack.length === 1) {
1933
1800
  const a = stack[0].type;
1934
1801
 
1935
- if (a !== 'operator') {
1802
+ // support for lambdas
1803
+
1804
+ if (element.type === 'group' && element.explicit) {
1805
+ if (a === 'address' || a === 'call' || a === 'identifier' || a === 'implicit-call') {
1806
+
1807
+ // our parser seems to create implicit groups from these
1808
+ // values in parens. we should fix that, but we can unpack it.
1809
+
1810
+ let args = element.elements;
1811
+ if (args.length === 1 && args[0].type === 'group' && !args[0].explicit) {
1812
+ args = args[0].elements;
1813
+ }
1814
+
1815
+ // create an implicit call. replace on the stack.
1816
+
1817
+ stack[0] = {
1818
+ type: 'implicit-call',
1819
+ call: stack[0],
1820
+ args,
1821
+ id: this.id_counter++,
1822
+ position: stack[0].position,
1823
+ };
1824
+
1825
+ continue;
1826
+
1827
+ }
1828
+ }
1829
+ /*
1830
+ else if (a !== 'operator') {
1936
1831
 
1937
1832
  // console.warn("unexpected element", stack[0], element);
1938
1833
 
@@ -1947,6 +1842,7 @@ export class Parser {
1947
1842
  };
1948
1843
 
1949
1844
  }
1845
+ */
1950
1846
 
1951
1847
  }
1952
1848
 
@@ -1996,6 +1892,8 @@ export class Parser {
1996
1892
  stack.splice(-2, 2, operation);
1997
1893
  }
1998
1894
  else {
1895
+
1896
+ /*
1999
1897
  this.error = `multiple expressions`;
2000
1898
  this.error_position = (element as {position?: number}).position;
2001
1899
  this.valid = false;
@@ -2005,9 +1903,22 @@ export class Parser {
2005
1903
  elements: stream,
2006
1904
  explicit: false,
2007
1905
  };
1906
+ */
1907
+
1908
+ stack.push(element);
1909
+
2008
1910
  }
2009
1911
  }
2010
1912
 
1913
+ if (stack.length > 1) {
1914
+ return {
1915
+ type: 'group',
1916
+ id: this.id_counter++,
1917
+ elements: stack,
1918
+ explicit: false,
1919
+ };
1920
+ }
1921
+
2011
1922
  return stack[0];
2012
1923
  }
2013
1924
 
@@ -2385,6 +2296,20 @@ export class Parser {
2385
2296
 
2386
2297
  this.ConsumeWhiteSpace();
2387
2298
 
2299
+ // UPDATE: UNLESS the token is an address, because that's not
2300
+ // a legal function name. so change that precedence rule, address
2301
+ // comes first.
2302
+
2303
+ // erm -- that's not 100% correct. LOG10 is a valid cell address
2304
+ // and a valid function name. there might be others as well.
2305
+
2306
+ if (this.flags.spreadsheet_semantics) {
2307
+ const address = this.ConsumeAddress(str, position);
2308
+ if (address) return address;
2309
+ }
2310
+
2311
+ // [FIXME: what about braces? (...)]
2312
+
2388
2313
  const next_char = this.data[this.index];
2389
2314
  if (next_char === OPEN_PAREN) {
2390
2315
  const args = this.ConsumeArguments();
@@ -2404,8 +2329,9 @@ export class Parser {
2404
2329
  // range operator, and a second address. that will be turned into a range
2405
2330
  // later.
2406
2331
 
2407
- const address = this.ConsumeAddress(str, position);
2408
- if (address) return address;
2332
+ // moved up
2333
+ // const address = this.ConsumeAddress(str, position);
2334
+ // if (address) return address;
2409
2335
 
2410
2336
  // check for structured reference, if we had square brackets
2411
2337
 
@@ -2638,6 +2564,14 @@ export class Parser {
2638
2564
  if (!r) return null;
2639
2565
  position = r.position;
2640
2566
 
2567
+ // special hack for LOG10. ugh. can't find any other functions with
2568
+ // this problem, in english at least. btw what's the translation for
2569
+ // log10?
2570
+
2571
+ if (c.column === 8508 && r.row === 9) {
2572
+ return null;
2573
+ }
2574
+
2641
2575
  const label = sheet ?
2642
2576
  sheet + token.substr(sheet.length, position - index).toUpperCase() :
2643
2577
  token.substr(0, position - index).toUpperCase();