@trebco/treb 31.8.2 → 32.1.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 (43) hide show
  1. package/dist/treb-spreadsheet.mjs +8 -8
  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 +2 -2
  13. package/treb-base-types/src/union.ts +6 -0
  14. package/treb-base-types/src/value-type.ts +13 -0
  15. package/treb-calculator/src/calculator.ts +18 -1
  16. package/treb-calculator/src/descriptors.ts +42 -3
  17. package/treb-calculator/src/expression-calculator.ts +182 -6
  18. package/treb-calculator/src/functions/base-functions.ts +48 -0
  19. package/treb-calculator/src/functions/lambda-functions.ts +96 -0
  20. package/treb-data-model/src/data_model.ts +28 -13
  21. package/treb-data-model/src/named.ts +7 -0
  22. package/treb-data-model/src/sheet.ts +17 -0
  23. package/treb-embed/src/embedded-spreadsheet.ts +12 -3
  24. package/treb-embed/style/autocomplete.scss +4 -0
  25. package/treb-embed/style/dropdown-select.scss +3 -0
  26. package/treb-embed/style/font-stacks.scss +2 -0
  27. package/treb-embed/style/grid.scss +11 -11
  28. package/treb-embed/style/layout.scss +12 -9
  29. package/treb-embed/style/mouse-mask.scss +3 -0
  30. package/treb-embed/style/note.scss +4 -0
  31. package/treb-embed/style/overlay-editor.scss +3 -0
  32. package/treb-embed/style/tab-bar.scss +4 -0
  33. package/treb-embed/style/table.scss +3 -0
  34. package/treb-embed/style/theme-defaults.scss +2 -22
  35. package/treb-embed/style/tooltip.scss +4 -0
  36. package/treb-embed/style/treb-spreadsheet-element.scss +1 -1
  37. package/treb-export/src/workbook-style2.ts +0 -39
  38. package/treb-grid/src/render/svg_selection_block.ts +1 -37
  39. package/treb-grid/src/types/grid.ts +4 -3
  40. package/treb-parser/src/parser-types.ts +21 -0
  41. package/treb-parser/src/parser.ts +126 -218
  42. package/dist/treb-export-worker.mjs +0 -2
  43. package/dist/treb.d.ts +0 -2235
@@ -78,7 +78,7 @@ export class SVGSelectionBlock {
78
78
  // and use currentColor, but we can't set opacity separately so we
79
79
  // need another node. which is a waste, but ergonomics ftw!
80
80
 
81
- this.fill = DOM.SVG('rect', 'fill');
81
+ this.fill = DOM.SVG('rect', 'treb-selection-fill');
82
82
 
83
83
  // this.SetThemeColor(0);
84
84
  // if (theme.additional_selection_line_dash_array) {
@@ -95,42 +95,6 @@ export class SVGSelectionBlock {
95
95
  this.g.setAttribute('transform', `translate(${offset.x}, ${offset.y})`);
96
96
  }
97
97
 
98
- /*
99
- public SetThemeColor(index = 0) {
100
-
101
- if (Array.isArray(this.theme.additional_selection_color)) {
102
- if (index >= this.theme.additional_selection_color.length) {
103
- index = index % this.theme.additional_selection_color.length;
104
- }
105
- }
106
-
107
- if (this.theme.additional_selection_overlay_color) {
108
- if (typeof this.theme.additional_selection_overlay_color === 'string') {
109
- this.outline.setAttribute('fill', this.theme.additional_selection_overlay_color);
110
- }
111
- else {
112
- this.outline.setAttribute('fill', this.theme.additional_selection_overlay_color[index] || '');
113
- }
114
- }
115
- else {
116
- this.outline.setAttribute('fill', '');
117
- }
118
-
119
- if (this.theme.additional_selection_color) {
120
- if (typeof this.theme.additional_selection_color === 'string') {
121
- this.outline.setAttribute('stroke', this.theme.additional_selection_color);
122
- }
123
- else {
124
- this.outline.setAttribute('stroke', this.theme.additional_selection_color[index] || '');
125
- }
126
- }
127
- else {
128
- this.outline.setAttribute('stroke', '');
129
- }
130
-
131
- }
132
- */
133
-
134
98
  public Show(show = true) {
135
99
  this.g.style.display = show ? 'block' : 'none';
136
100
  }
@@ -1284,7 +1284,7 @@ export class Grid extends GridBase {
1284
1284
  this.model.named.Reset();
1285
1285
 
1286
1286
  if (import_data.named) {
1287
- this.model.UnserializeNames(import_data.named, this.active_sheet);
1287
+ this.model.UnserializeNames(import_data.named, this.active_sheet, true);
1288
1288
  }
1289
1289
 
1290
1290
  // FIXME: do we need to rebuild autocomplete here (A: yes)
@@ -2350,11 +2350,12 @@ export class Grid extends GridBase {
2350
2350
 
2351
2351
 
2352
2352
  // this is public so we need to (un)translate.
2353
- data = this.model.UntranslateData(data);
2353
+ // console.info("PRE", {r1c1}, data);
2354
+ data = this.model.UntranslateData(data, { r1c1 });
2355
+ // console.info("POST", data, "\n");
2354
2356
 
2355
2357
  // single value, easiest
2356
2358
  if (!Array.isArray(data)) {
2357
-
2358
2359
  if (recycle || array) {
2359
2360
  this.ExecCommand({ key: CommandKey.SetRange, area: range, value: data, array, r1c1 });
2360
2361
  }
@@ -167,6 +167,17 @@ export interface UnitCall extends BaseUnit {
167
167
 
168
168
  }
169
169
 
170
+ /**
171
+ * new call type: implicit. we might merge these.
172
+ */
173
+ export interface UnitImplicitCall extends BaseUnit {
174
+ type: 'implicit-call';
175
+ position: number;
176
+ args: ExpressionUnit[];
177
+ call: ExpressionUnit;
178
+
179
+ }
180
+
170
181
  /**
171
182
  * this isn't an output type (unless parsing fails), but it's useful
172
183
  * to be able to pass these around with the same semantics.
@@ -177,6 +188,14 @@ export interface UnitOperator extends BaseUnit {
177
188
  operator: string;
178
189
  }
179
190
 
191
+ /**
192
+ * also not an output type
193
+ */
194
+ export interface UnitGroupSeparator extends BaseUnit {
195
+ type: 'group-separator';
196
+ position: number;
197
+ }
198
+
180
199
  /**
181
200
  * expression unit representing a binary operation. operations may be
182
201
  * re-ordered based on precendence.
@@ -259,9 +278,11 @@ export type BaseExpressionUnit =
259
278
  | UnitArray
260
279
  | UnitIdentifier
261
280
  | UnitCall
281
+ | UnitImplicitCall
262
282
  | UnitMissing
263
283
  | UnitGroup
264
284
  | UnitOperator
285
+ | UnitGroupSeparator
265
286
  | UnitBinary
266
287
  | UnitUnary
267
288
  | UnitAddress
@@ -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) {
@@ -755,6 +771,10 @@ export class Parser {
755
771
  .map((x) => this.Render(x, options)).join(separator);
756
772
  }
757
773
 
774
+ case 'implicit-call':
775
+ return this.Render(unit.call, options) +
776
+ '(' + unit.args.map(element => this.Render(element, options)).join(separator) + ')';
777
+
758
778
  case 'call':
759
779
  return (
760
780
  unit.name +
@@ -948,8 +968,13 @@ export class Parser {
948
968
  '\'' + address.sheet + '\'' : address.sheet) + '!';
949
969
  }
950
970
 
971
+ const row = address.offset_row ? `[${address.row}]` : address.row + 1;
972
+ const column = address.offset_column ? `[${address.column}]` : address.column + 1;
973
+
974
+ /*
951
975
  const row = (address.absolute_row || !base) ? (address.row + 1).toString() : `[${address.row - base.row}]`;
952
976
  const column = (address.absolute_column || !base) ? (address.column + 1).toString() : `[${address.column - base.column}]`;
977
+ */
953
978
 
954
979
  label += `R${row}C${column}`;
955
980
 
@@ -1009,11 +1034,12 @@ export class Parser {
1009
1034
  *
1010
1035
  * @param exit exit on specific characters
1011
1036
  */
1012
- protected ParseGeneric(exit: number[] = [0]): ExpressionUnit | null {
1037
+ protected ParseGeneric(exit: number[] = [0], explicit_group = false): ExpressionUnit | null {
1013
1038
  let stream: ExpressionUnit[] = [];
1014
1039
 
1015
1040
  for (; this.index < this.length;) {
1016
1041
  const unit = this.ParseNext(stream.length === 0);
1042
+
1017
1043
  if (typeof unit === 'number') {
1018
1044
 
1019
1045
  if (exit.some((test) => unit === test)) {
@@ -1025,8 +1051,11 @@ export class Parser {
1025
1051
  // so we only have to worry about grouping. parse
1026
1052
  // up to the closing paren...
1027
1053
 
1054
+ // actually now we have implicit calls, so we need
1055
+ // to manage that here.
1056
+
1028
1057
  this.index++; // open paren
1029
- const group = this.ParseGeneric([CLOSE_PAREN]);
1058
+ const group = this.ParseGeneric([CLOSE_PAREN], true);
1030
1059
  this.index++; // close paren
1031
1060
 
1032
1061
  // and wrap up in a group element to prevent reordering.
@@ -1051,6 +1080,21 @@ export class Parser {
1051
1080
  if (operator) {
1052
1081
  stream.push(operator);
1053
1082
  }
1083
+ else if (explicit_group && unit === this.argument_separator_char) {
1084
+
1085
+ // adding a new unit type here to explicitly show we're in
1086
+ // a group; prevents later passes from treating arguments as
1087
+ // fractions or something else. we just need to remove these
1088
+ // later
1089
+
1090
+ stream.push({
1091
+ type: 'group-separator',
1092
+ position: this.index,
1093
+ id: this.id_counter++,
1094
+ });
1095
+
1096
+ this.index++;
1097
+ }
1054
1098
  else {
1055
1099
  this.error = `unexpected character [1]: ${String.fromCharCode(unit)}, 0x${unit.toString(16)}`;
1056
1100
  this.valid = false;
@@ -1231,13 +1275,6 @@ export class Parser {
1231
1275
  // fix ordering of binary operations based on precedence; also
1232
1276
  // convert and validate ranges
1233
1277
 
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
1278
  return this.BinaryToComplex(this.ArrangeUnits(stream));
1242
1279
 
1243
1280
  }
@@ -1612,206 +1649,13 @@ export class Parser {
1612
1649
 
1613
1650
 
1614
1651
  /**
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
1652
+ * reorders operations for precendence
1642
1653
  *
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 (...)
1654
+ * this method was written with the assumption that groups were
1655
+ * always an error. that's no longer true, with implicit calls.
1656
+ * we should still error if it's not an _explicit_ group, i.e. there's
1657
+ * just a bunch of naked tokens.
1647
1658
  *
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
1659
  */
1816
1660
  protected ArrangeUnits(stream: ExpressionUnit[]): ExpressionUnit {
1817
1661
 
@@ -1838,6 +1682,10 @@ export class Parser {
1838
1682
  for (let index = 0; index < stream.length; index++) {
1839
1683
  let element = stream[index];
1840
1684
 
1685
+ if (element.type === 'group-separator') {
1686
+ continue; // drop
1687
+ }
1688
+
1841
1689
  // given that we need to support unary operators, the logic needs
1842
1690
  // to be a little different. operators are OK at any position, provided
1843
1691
  // we can construct either a unary or binary operation.
@@ -1848,14 +1696,7 @@ export class Parser {
1848
1696
  // in this case we do it with recursion.
1849
1697
 
1850
1698
  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));
1699
+
1859
1700
  const right = this.BinaryToComplex(this.ArrangeUnits(stream.slice(index + 1)));
1860
1701
 
1861
1702
  // this ensures we return the highest-level group, even if we recurse
@@ -1932,7 +1773,35 @@ export class Parser {
1932
1773
  if (stack.length === 1) {
1933
1774
  const a = stack[0].type;
1934
1775
 
1935
- if (a !== 'operator') {
1776
+ // support for lambdas
1777
+
1778
+ if (element.type === 'group' && element.explicit) {
1779
+ if (a === 'address' || a === 'call' || a === 'identifier' || a === 'implicit-call') {
1780
+
1781
+ // our parser seems to create implicit groups from these
1782
+ // values in parens. we should fix that, but we can unpack it.
1783
+
1784
+ let args = element.elements;
1785
+ if (args.length === 1 && args[0].type === 'group' && !args[0].explicit) {
1786
+ args = args[0].elements;
1787
+ }
1788
+
1789
+ // create an implicit call. replace on the stack.
1790
+
1791
+ stack[0] = {
1792
+ type: 'implicit-call',
1793
+ call: stack[0],
1794
+ args,
1795
+ id: this.id_counter++,
1796
+ position: stack[0].position,
1797
+ };
1798
+
1799
+ continue;
1800
+
1801
+ }
1802
+ }
1803
+ /*
1804
+ else if (a !== 'operator') {
1936
1805
 
1937
1806
  // console.warn("unexpected element", stack[0], element);
1938
1807
 
@@ -1947,6 +1816,7 @@ export class Parser {
1947
1816
  };
1948
1817
 
1949
1818
  }
1819
+ */
1950
1820
 
1951
1821
  }
1952
1822
 
@@ -1996,6 +1866,8 @@ export class Parser {
1996
1866
  stack.splice(-2, 2, operation);
1997
1867
  }
1998
1868
  else {
1869
+
1870
+ /*
1999
1871
  this.error = `multiple expressions`;
2000
1872
  this.error_position = (element as {position?: number}).position;
2001
1873
  this.valid = false;
@@ -2005,9 +1877,22 @@ export class Parser {
2005
1877
  elements: stream,
2006
1878
  explicit: false,
2007
1879
  };
1880
+ */
1881
+
1882
+ stack.push(element);
1883
+
2008
1884
  }
2009
1885
  }
2010
1886
 
1887
+ if (stack.length > 1) {
1888
+ return {
1889
+ type: 'group',
1890
+ id: this.id_counter++,
1891
+ elements: stack,
1892
+ explicit: false,
1893
+ };
1894
+ }
1895
+
2011
1896
  return stack[0];
2012
1897
  }
2013
1898
 
@@ -2385,6 +2270,20 @@ export class Parser {
2385
2270
 
2386
2271
  this.ConsumeWhiteSpace();
2387
2272
 
2273
+ // UPDATE: UNLESS the token is an address, because that's not
2274
+ // a legal function name. so change that precedence rule, address
2275
+ // comes first.
2276
+
2277
+ // erm -- that's not 100% correct. LOG10 is a valid cell address
2278
+ // and a valid function name. there might be others as well.
2279
+
2280
+ if (this.flags.spreadsheet_semantics) {
2281
+ const address = this.ConsumeAddress(str, position);
2282
+ if (address) return address;
2283
+ }
2284
+
2285
+ // [FIXME: what about braces? (...)]
2286
+
2388
2287
  const next_char = this.data[this.index];
2389
2288
  if (next_char === OPEN_PAREN) {
2390
2289
  const args = this.ConsumeArguments();
@@ -2404,8 +2303,9 @@ export class Parser {
2404
2303
  // range operator, and a second address. that will be turned into a range
2405
2304
  // later.
2406
2305
 
2407
- const address = this.ConsumeAddress(str, position);
2408
- if (address) return address;
2306
+ // moved up
2307
+ // const address = this.ConsumeAddress(str, position);
2308
+ // if (address) return address;
2409
2309
 
2410
2310
  // check for structured reference, if we had square brackets
2411
2311
 
@@ -2638,6 +2538,14 @@ export class Parser {
2638
2538
  if (!r) return null;
2639
2539
  position = r.position;
2640
2540
 
2541
+ // special hack for LOG10. ugh. can't find any other functions with
2542
+ // this problem, in english at least. btw what's the translation for
2543
+ // log10?
2544
+
2545
+ if (c.column === 8508 && r.row === 9) {
2546
+ return null;
2547
+ }
2548
+
2641
2549
  const label = sheet ?
2642
2550
  sheet + token.substr(sheet.length, position - index).toUpperCase() :
2643
2551
  token.substr(0, position - index).toUpperCase();