@trebco/treb 32.1.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.
- package/dist/treb-spreadsheet.mjs +15 -15
- package/package.json +1 -1
- package/treb-base-types/src/style.ts +20 -0
- package/treb-calculator/src/calculator.ts +2 -0
- package/treb-calculator/src/descriptors.ts +7 -1
- package/treb-calculator/src/expression-calculator.ts +129 -54
- package/treb-calculator/src/functions/base-functions.ts +1 -0
- package/treb-calculator/src/functions/fp.ts +339 -0
- package/treb-calculator/src/functions/gamma.ts +143 -0
- package/treb-calculator/src/functions/statistics-functions.ts +88 -0
- package/treb-data-model/src/sheet.ts +2 -2
- package/treb-embed/src/embedded-spreadsheet.ts +10 -2
- package/treb-grid/src/editors/editor.ts +14 -0
- package/treb-grid/src/types/grid.ts +70 -25
- package/treb-parser/src/parser-types.ts +3 -0
- package/treb-parser/src/parser.ts +33 -7
- package/treb-calculator/tsconfig.json +0 -7
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* functions and constants for gamma distribution. we're
|
|
3
|
+
* returning false instead of throwing so we can return
|
|
4
|
+
* a spreadsheet-style eror. TODO: optional?
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const max_iterations = 1000;
|
|
8
|
+
const epsilon = 3.0e-9;
|
|
9
|
+
const min_value = 1.0e-30;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* faster approximation for real numbers
|
|
13
|
+
*/
|
|
14
|
+
export const gamma_ln = (z: number): number => {
|
|
15
|
+
|
|
16
|
+
let x = z - 1.0;
|
|
17
|
+
let y = x + 5.5;
|
|
18
|
+
y -= (x + 0.5) * Math.log(y);
|
|
19
|
+
let a = 1.0;
|
|
20
|
+
|
|
21
|
+
const coefficients = [
|
|
22
|
+
76.18009173,
|
|
23
|
+
-86.50532033,
|
|
24
|
+
24.01409822,
|
|
25
|
+
-1.231739516,
|
|
26
|
+
0.120858003e-2,
|
|
27
|
+
-0.536382e-5,
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
for (const coeff of coefficients) {
|
|
31
|
+
a += coeff / (++x);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return (-y + Math.log(2.50662827465 * a));
|
|
35
|
+
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* series representation
|
|
40
|
+
*/
|
|
41
|
+
export const gamma_series = (a: number, x: number): number|false => {
|
|
42
|
+
|
|
43
|
+
const gamma_ln_a = gamma_ln(a);
|
|
44
|
+
|
|
45
|
+
if (x === 0) {
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let ap = a;
|
|
50
|
+
let sum = 1.0 / a;
|
|
51
|
+
let del = sum;
|
|
52
|
+
for (let n = 1; n < max_iterations; n++) {
|
|
53
|
+
++ap;
|
|
54
|
+
del *= x / ap;
|
|
55
|
+
sum += del;
|
|
56
|
+
if (Math.abs(del) < Math.abs(sum) * epsilon) {
|
|
57
|
+
return sum * Math.exp(-x + a * Math.log(x) - (gamma_ln_a));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// throw new Error('too many iterations');
|
|
62
|
+
console.error('too many iterations');
|
|
63
|
+
return false;
|
|
64
|
+
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* continued fraction
|
|
69
|
+
*/
|
|
70
|
+
export const gamma_cf = (a: number, x: number): number|false => {
|
|
71
|
+
|
|
72
|
+
const gamma_ln_a = gamma_ln(a);
|
|
73
|
+
|
|
74
|
+
let b = x + 1.0 - a;
|
|
75
|
+
let c = 1.0 / min_value;
|
|
76
|
+
let d = 1.0 / b;
|
|
77
|
+
let h = d;
|
|
78
|
+
|
|
79
|
+
for (let i = 1; i <= max_iterations; i++) {
|
|
80
|
+
|
|
81
|
+
const an = -i * (i - a); b += 2.0; d = an * d + b;
|
|
82
|
+
|
|
83
|
+
if (Math.abs(d) < min_value) d = min_value;
|
|
84
|
+
c = b + an / c;
|
|
85
|
+
|
|
86
|
+
if (Math.abs(c) < min_value) c = min_value;
|
|
87
|
+
|
|
88
|
+
d = 1.0 / d;
|
|
89
|
+
const del = d * c;
|
|
90
|
+
h *= del;
|
|
91
|
+
|
|
92
|
+
if (Math.abs(del - 1.0) < epsilon) {
|
|
93
|
+
return Math.exp(-x + a * Math.log(x) - (gamma_ln_a)) * h;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// throw new Error('too many iterations');
|
|
99
|
+
console.error('too many iterations');
|
|
100
|
+
return false;
|
|
101
|
+
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* regularized gamma function; CDF of gamma distribution with scale 1
|
|
106
|
+
*/
|
|
107
|
+
export const gamma_p = (a: number, x: number) => {
|
|
108
|
+
|
|
109
|
+
if (x < 0.0 || a <= 0.0) {
|
|
110
|
+
// throw new Error('invalid parameter');
|
|
111
|
+
console.error('invalid parameter');
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
if (x < (a + 1.0)) {
|
|
115
|
+
return gamma_series(a, x);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
const cf = gamma_cf(a, x);
|
|
119
|
+
if (cf === false) { return false; }
|
|
120
|
+
return 1 - cf;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* regularized gamma function
|
|
127
|
+
*/
|
|
128
|
+
export const gamma_q = (a: number, x: number) => {
|
|
129
|
+
|
|
130
|
+
if (x < 0.0 || a <= 0.0) {
|
|
131
|
+
// throw new Error('invalid parameter');
|
|
132
|
+
console.error('invalid parameter');
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
if (x < (a + 1.0)) {
|
|
136
|
+
const series = gamma_series(a, x);
|
|
137
|
+
if (series === false) { return false; }
|
|
138
|
+
return 1 - series;
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
return gamma_cf(a, x);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -26,6 +26,7 @@ import { type Complex, type UnionValue, ValueType, type CellValue, ComplexOrReal
|
|
|
26
26
|
import * as ComplexMath from '../complex-math';
|
|
27
27
|
|
|
28
28
|
import { BetaCDF, BetaPDF, InverseBeta, LnGamma } from './beta';
|
|
29
|
+
import { gamma_p } from './gamma';
|
|
29
30
|
|
|
30
31
|
/** error function (for gaussian distribution) */
|
|
31
32
|
const erf = (x: number): number => {
|
|
@@ -172,6 +173,46 @@ const Gamma = (z: Complex): Complex => {
|
|
|
172
173
|
|
|
173
174
|
};
|
|
174
175
|
|
|
176
|
+
const GammaPDF = (x: number, alpha: number, beta: number): number => {
|
|
177
|
+
const gamma_alpha = Gamma({real: alpha, imaginary: 0}).real;
|
|
178
|
+
return (Math.pow(x, alpha - 1) * Math.exp(-x / beta)) / (gamma_alpha * Math.pow(beta, alpha));
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
/** bisection */
|
|
182
|
+
const InverseGamma = (p: number, alpha: number, beta: number): number|false => {
|
|
183
|
+
|
|
184
|
+
let lower = 0;
|
|
185
|
+
let upper = alpha * 10;
|
|
186
|
+
|
|
187
|
+
const tolerance = 1e-6;
|
|
188
|
+
|
|
189
|
+
let iterations = 0;
|
|
190
|
+
|
|
191
|
+
while (upper - lower > tolerance) {
|
|
192
|
+
iterations++;
|
|
193
|
+
|
|
194
|
+
const mid = (upper + lower) / 2;
|
|
195
|
+
|
|
196
|
+
const f_lower = gamma_p(alpha, lower/beta);
|
|
197
|
+
const f_mid = gamma_p(alpha, mid/beta);
|
|
198
|
+
|
|
199
|
+
if (f_lower === false || f_mid === false) {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if ((f_mid - p) * (f_lower - p) < 0) {
|
|
204
|
+
upper = mid;
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
lower = mid;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return (lower + upper) / 2;
|
|
213
|
+
|
|
214
|
+
};
|
|
215
|
+
|
|
175
216
|
export const Variance = (data: number[], sample = false) => {
|
|
176
217
|
|
|
177
218
|
const len = data.length;
|
|
@@ -743,6 +784,53 @@ export const StatisticsFunctionLibrary: FunctionMap = {
|
|
|
743
784
|
},
|
|
744
785
|
},
|
|
745
786
|
|
|
787
|
+
'Gamma.Inv': {
|
|
788
|
+
description: 'Returns the inverse of the gamma distribution',
|
|
789
|
+
arguments: [
|
|
790
|
+
{name: 'probability', unroll: true},
|
|
791
|
+
{name: 'alpha', },
|
|
792
|
+
{name: 'beta', },
|
|
793
|
+
],
|
|
794
|
+
fn: (p: number, alpha: number, beta: number) => {
|
|
795
|
+
|
|
796
|
+
if (p < 0 || p > 1) {
|
|
797
|
+
return ArgumentError();
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
const value = InverseGamma(p, alpha, beta);
|
|
801
|
+
|
|
802
|
+
if (value === false) {
|
|
803
|
+
return ValueError();
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
return {
|
|
807
|
+
type: ValueType.number,
|
|
808
|
+
value,
|
|
809
|
+
}
|
|
810
|
+
},
|
|
811
|
+
},
|
|
812
|
+
|
|
813
|
+
'Gamma.Dist': {
|
|
814
|
+
fn: (x: number, alpha: number, beta: number, cumulative?: boolean) => {
|
|
815
|
+
|
|
816
|
+
if (x < 0 || alpha <= 0 || beta <= 0) {
|
|
817
|
+
return ArgumentError();
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
const value = cumulative ? gamma_p(alpha, x/beta) : GammaPDF(x, alpha, beta);
|
|
821
|
+
|
|
822
|
+
if (value === false) {
|
|
823
|
+
return ValueError();
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
return {
|
|
827
|
+
type: ValueType.number,
|
|
828
|
+
value,
|
|
829
|
+
};
|
|
830
|
+
|
|
831
|
+
}
|
|
832
|
+
},
|
|
833
|
+
|
|
746
834
|
GammaLn: {
|
|
747
835
|
description: 'Returns the natural log of the gamma function',
|
|
748
836
|
arguments: [{ name: 'value', boxed: true, unroll: true }],
|
|
@@ -2556,7 +2556,7 @@ export class Sheet {
|
|
|
2556
2556
|
cell_reference_map[c] = [];
|
|
2557
2557
|
for (let r = 0; r < column.length; r++) {
|
|
2558
2558
|
if (column[r]) {
|
|
2559
|
-
const style_as_json = JSON.stringify(column[r]);
|
|
2559
|
+
const style_as_json = Style.Serialize(column[r]); // JSON.stringify(column[r]);
|
|
2560
2560
|
if (style_as_json !== empty_json) {
|
|
2561
2561
|
let reference_index = cell_style_map[style_as_json];
|
|
2562
2562
|
if (typeof reference_index !== 'number') {
|
|
@@ -2579,7 +2579,7 @@ export class Sheet {
|
|
|
2579
2579
|
*/
|
|
2580
2580
|
const StyleToRef = (style: CellStyle) => {
|
|
2581
2581
|
|
|
2582
|
-
const style_as_json = JSON.stringify(style);
|
|
2582
|
+
const style_as_json = Style.Serialize(style); // JSON.stringify(style);
|
|
2583
2583
|
if (style_as_json === empty_json) {
|
|
2584
2584
|
return 0;
|
|
2585
2585
|
}
|
|
@@ -2242,8 +2242,11 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
2242
2242
|
|
|
2243
2243
|
// console.info({events});
|
|
2244
2244
|
|
|
2245
|
+
// FIXME: composite? (...)
|
|
2246
|
+
|
|
2245
2247
|
for (const event of events) {
|
|
2246
2248
|
switch (event.type) {
|
|
2249
|
+
case 'composite':
|
|
2247
2250
|
case 'data':
|
|
2248
2251
|
recalc = true;
|
|
2249
2252
|
break;
|
|
@@ -4000,8 +4003,13 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
4000
4003
|
// API v1 OK
|
|
4001
4004
|
|
|
4002
4005
|
let area: Area | undefined;
|
|
4003
|
-
|
|
4004
|
-
|
|
4006
|
+
switch (event?.type) {
|
|
4007
|
+
case 'data':
|
|
4008
|
+
area = event.area;
|
|
4009
|
+
break;
|
|
4010
|
+
case 'composite':
|
|
4011
|
+
area = event.data_area;
|
|
4012
|
+
break;
|
|
4005
4013
|
}
|
|
4006
4014
|
|
|
4007
4015
|
this.calculator.Calculate(area);
|
|
@@ -1036,11 +1036,25 @@ export class Editor<E = FormulaEditorEvent> extends EventSource<E|FormulaEditorE
|
|
|
1036
1036
|
if (matcher) {
|
|
1037
1037
|
Promise.resolve().then(() => {
|
|
1038
1038
|
const exec_result = matcher.Exec({ text, cursor: substring_end.length });
|
|
1039
|
+
|
|
1040
|
+
// fix behavior around "f16round", which is a function we accidentally
|
|
1041
|
+
// inherit from Math in Safari and Firefox. it breaks arrow-selection
|
|
1042
|
+
// because it pops up an AC window. we don't want to break AC behavior
|
|
1043
|
+
// on tooltips, though, so we still want to call AC methods. just block
|
|
1044
|
+
// the completion list.
|
|
1045
|
+
|
|
1046
|
+
if (this.selecting && exec_result.completions?.length) {
|
|
1047
|
+
// console.info("Blocking completion list", JSON.stringify(exec_result.completions));
|
|
1048
|
+
exec_result.completions = undefined;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1039
1051
|
const node =
|
|
1040
1052
|
this.NodeAtIndex(exec_result.completions?.length ?
|
|
1041
1053
|
(exec_result.position || 0) :
|
|
1042
1054
|
(exec_result.function_position || 0));
|
|
1055
|
+
|
|
1043
1056
|
this.Autocomplete(exec_result, node);
|
|
1057
|
+
|
|
1044
1058
|
});
|
|
1045
1059
|
}
|
|
1046
1060
|
|
|
@@ -2041,6 +2041,7 @@ export class Grid extends GridBase {
|
|
|
2041
2041
|
command.expression = parse_result.expression;
|
|
2042
2042
|
}
|
|
2043
2043
|
else {
|
|
2044
|
+
console.info({expression, parse_result});
|
|
2044
2045
|
throw new Error('invalid expression');
|
|
2045
2046
|
}
|
|
2046
2047
|
}
|
|
@@ -4554,20 +4555,8 @@ export class Grid extends GridBase {
|
|
|
4554
4555
|
if (selecting_argument) {
|
|
4555
4556
|
this.UpdateSelectedArgument(selection);
|
|
4556
4557
|
}
|
|
4557
|
-
else
|
|
4558
|
-
|
|
4559
|
-
this.UpdateAddressLabel(undefined, selection.area.columns + 'C');
|
|
4560
|
-
}
|
|
4561
|
-
else if (selection.area.entire_row) {
|
|
4562
|
-
this.UpdateAddressLabel(undefined, selection.area.rows + 'R');
|
|
4563
|
-
}
|
|
4564
|
-
else if (selection.area.count > 1) {
|
|
4565
|
-
this.UpdateAddressLabel(undefined, selection.area.rows + 'R x ' +
|
|
4566
|
-
selection.area.columns + 'C');
|
|
4567
|
-
}
|
|
4568
|
-
else {
|
|
4569
|
-
this.UpdateAddressLabel(selection);
|
|
4570
|
-
}
|
|
4558
|
+
else {
|
|
4559
|
+
this.UpdateAddressLabelArea(selection); // 3R x 2C
|
|
4571
4560
|
}
|
|
4572
4561
|
}
|
|
4573
4562
|
}, () => {
|
|
@@ -5114,6 +5103,9 @@ export class Grid extends GridBase {
|
|
|
5114
5103
|
|
|
5115
5104
|
if (!selection.empty && (delta.columns || delta.rows)) {
|
|
5116
5105
|
if (this.BlockSelection(selection, !!event.shiftKey, delta.columns, delta.rows)) {
|
|
5106
|
+
if (event.shiftKey && !selecting_argument) {
|
|
5107
|
+
this.UpdateAddressLabelArea(selection);
|
|
5108
|
+
}
|
|
5117
5109
|
return;
|
|
5118
5110
|
}
|
|
5119
5111
|
}
|
|
@@ -5280,6 +5272,9 @@ export class Grid extends GridBase {
|
|
|
5280
5272
|
|
|
5281
5273
|
if (delta.rows || delta.columns) {
|
|
5282
5274
|
this.AdvanceSelection(delta, selection, within_selection, expand_selection, !editor_open);
|
|
5275
|
+
if (event.shiftKey && !selecting_argument) {
|
|
5276
|
+
this.UpdateAddressLabelArea(selection);
|
|
5277
|
+
}
|
|
5283
5278
|
}
|
|
5284
5279
|
|
|
5285
5280
|
}
|
|
@@ -5660,7 +5655,13 @@ export class Grid extends GridBase {
|
|
|
5660
5655
|
}
|
|
5661
5656
|
}
|
|
5662
5657
|
}
|
|
5663
|
-
if (formula_parse_result.
|
|
5658
|
+
if (formula_parse_result.expression) {
|
|
5659
|
+
|
|
5660
|
+
// we were previously looking at address arguments, and ignoring
|
|
5661
|
+
// ranges. this leads to inconsistent behavior. we'll now look at
|
|
5662
|
+
// ranges when inferring number format.
|
|
5663
|
+
|
|
5664
|
+
// there was also an issue with using the correct sheet, also fixed.
|
|
5664
5665
|
|
|
5665
5666
|
// this was set up to just use the first format we found.
|
|
5666
5667
|
// updating to change priority -- if the first one is a
|
|
@@ -5669,14 +5670,10 @@ export class Grid extends GridBase {
|
|
|
5669
5670
|
|
|
5670
5671
|
let found_number_format: string|undefined = undefined;
|
|
5671
5672
|
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
// FIXME: this should not be active_sheet
|
|
5678
|
-
|
|
5679
|
-
const test = this.active_sheet.CellData({ ...address });
|
|
5673
|
+
/** returns true if we've found a non-% number format */
|
|
5674
|
+
const Check = (address: ICellAddress, sheet = this.active_sheet) => {
|
|
5675
|
+
if (sheet.HasCellStyle({...address})) {
|
|
5676
|
+
const test = sheet.CellData({ ...address });
|
|
5680
5677
|
if (test.style && test.style.number_format) {
|
|
5681
5678
|
if (!found_number_format || /%/.test(found_number_format)) {
|
|
5682
5679
|
|
|
@@ -5686,13 +5683,43 @@ export class Grid extends GridBase {
|
|
|
5686
5683
|
|
|
5687
5684
|
found_number_format = NumberFormatCache.Translate(test.style.number_format);
|
|
5688
5685
|
if (!/%/.test(found_number_format)) {
|
|
5689
|
-
|
|
5686
|
+
return true;
|
|
5690
5687
|
}
|
|
5691
5688
|
|
|
5692
5689
|
}
|
|
5693
5690
|
}
|
|
5694
5691
|
}
|
|
5695
|
-
|
|
5692
|
+
return false;
|
|
5693
|
+
};
|
|
5694
|
+
|
|
5695
|
+
let finished = false;
|
|
5696
|
+
let sheet: Sheet|undefined = this.active_sheet;
|
|
5697
|
+
|
|
5698
|
+
this.parser.Walk(formula_parse_result.expression, unit => {
|
|
5699
|
+
|
|
5700
|
+
if (finished) {
|
|
5701
|
+
return false;
|
|
5702
|
+
}
|
|
5703
|
+
|
|
5704
|
+
switch (unit.type) {
|
|
5705
|
+
case 'address':
|
|
5706
|
+
this.model.ResolveSheetID(unit);
|
|
5707
|
+
sheet = this.model.sheets.Find(unit.sheet_id || 0);
|
|
5708
|
+
finished = finished || Check(unit, sheet);
|
|
5709
|
+
break;
|
|
5710
|
+
|
|
5711
|
+
case 'range':
|
|
5712
|
+
this.model.ResolveSheetID(unit);
|
|
5713
|
+
sheet = this.model.sheets.Find(unit.start.sheet_id || 0);
|
|
5714
|
+
for (const address of new Area(unit.start, unit.end)) {
|
|
5715
|
+
finished = finished || Check(address, sheet);
|
|
5716
|
+
if (finished) { break; }
|
|
5717
|
+
}
|
|
5718
|
+
break;
|
|
5719
|
+
}
|
|
5720
|
+
return !finished;
|
|
5721
|
+
|
|
5722
|
+
});
|
|
5696
5723
|
|
|
5697
5724
|
if (found_number_format) {
|
|
5698
5725
|
|
|
@@ -7025,6 +7052,24 @@ export class Grid extends GridBase {
|
|
|
7025
7052
|
}
|
|
7026
7053
|
}
|
|
7027
7054
|
|
|
7055
|
+
private UpdateAddressLabelArea(selection: GridSelection) {
|
|
7056
|
+
if (!selection.empty && !selection.area.entire_sheet) {
|
|
7057
|
+
if (selection.area.entire_column) {
|
|
7058
|
+
this.UpdateAddressLabel(undefined, selection.area.columns + 'C');
|
|
7059
|
+
}
|
|
7060
|
+
else if (selection.area.entire_row) {
|
|
7061
|
+
this.UpdateAddressLabel(undefined, selection.area.rows + 'R');
|
|
7062
|
+
}
|
|
7063
|
+
else if (selection.area.count > 1) {
|
|
7064
|
+
this.UpdateAddressLabel(undefined, selection.area.rows + 'R x ' +
|
|
7065
|
+
selection.area.columns + 'C');
|
|
7066
|
+
}
|
|
7067
|
+
else {
|
|
7068
|
+
this.UpdateAddressLabel(selection);
|
|
7069
|
+
}
|
|
7070
|
+
}
|
|
7071
|
+
}
|
|
7072
|
+
|
|
7028
7073
|
private UpdateAddressLabel(selection = this.primary_selection, text?: string) {
|
|
7029
7074
|
|
|
7030
7075
|
if (!this.formula_bar) { return; }
|
|
@@ -445,6 +445,9 @@ export interface RenderOptions {
|
|
|
445
445
|
/** base for offsetting relative R1C1 addresses */
|
|
446
446
|
r1c1_base?: UnitAddress;
|
|
447
447
|
|
|
448
|
+
/** force addresses to be relative */
|
|
449
|
+
r1c1_force_relative?: boolean;
|
|
450
|
+
|
|
448
451
|
/** if we're just translating, don't have to render addresses */
|
|
449
452
|
pass_through_addresses?: boolean;
|
|
450
453
|
|
|
@@ -602,14 +602,15 @@ export class Parser {
|
|
|
602
602
|
if (options.pass_through_addresses) {
|
|
603
603
|
return unit.label;
|
|
604
604
|
}
|
|
605
|
-
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);
|
|
606
606
|
|
|
607
607
|
case 'range':
|
|
608
608
|
if (options.pass_through_addresses) {
|
|
609
609
|
return unit.label;
|
|
610
610
|
}
|
|
611
611
|
return options.r1c1 ?
|
|
612
|
-
this.R1C1Label(unit.start, 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) :
|
|
613
614
|
this.AddressLabel(unit.start, offset) + ':' + this.AddressLabel(unit.end, offset);
|
|
614
615
|
|
|
615
616
|
case 'missing':
|
|
@@ -955,10 +956,14 @@ export class Parser {
|
|
|
955
956
|
|
|
956
957
|
/**
|
|
957
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...
|
|
958
962
|
*/
|
|
959
963
|
protected R1C1Label(
|
|
960
964
|
address: UnitAddress,
|
|
961
965
|
base?: UnitAddress,
|
|
966
|
+
force_relative = false,
|
|
962
967
|
): string {
|
|
963
968
|
|
|
964
969
|
let label = '';
|
|
@@ -968,8 +973,25 @@ export class Parser {
|
|
|
968
973
|
'\'' + address.sheet + '\'' : address.sheet) + '!';
|
|
969
974
|
}
|
|
970
975
|
|
|
971
|
-
|
|
972
|
-
|
|
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
|
+
}
|
|
973
995
|
|
|
974
996
|
/*
|
|
975
997
|
const row = (address.absolute_row || !base) ? (address.row + 1).toString() : `[${address.row - base.row}]`;
|
|
@@ -1063,14 +1085,18 @@ export class Parser {
|
|
|
1063
1085
|
|
|
1064
1086
|
// skip nulls
|
|
1065
1087
|
|
|
1066
|
-
|
|
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) {
|
|
1067
1092
|
stream.push({
|
|
1068
1093
|
type: 'group',
|
|
1069
1094
|
id: this.id_counter++,
|
|
1070
|
-
elements: [group],
|
|
1095
|
+
elements: group? [group] : [],
|
|
1071
1096
|
explicit: true,
|
|
1072
1097
|
});
|
|
1073
|
-
}
|
|
1098
|
+
//}
|
|
1099
|
+
|
|
1074
1100
|
}
|
|
1075
1101
|
else {
|
|
1076
1102
|
// this can probably move to PNext? except for the test
|