@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trebco/treb",
3
- "version": "32.1.1",
3
+ "version": "32.3.1",
4
4
  "license": "LGPL-3.0-or-later",
5
5
  "homepage": "https://treb.app",
6
6
  "repository": {
@@ -318,6 +318,26 @@ export const Style = {
318
318
  };
319
319
  },
320
320
 
321
+ Serialize: (style: CellStyle): string => {
322
+ const clone: CellStyle = JSON.parse(JSON.stringify(style));
323
+
324
+ // scrub border colors without widths
325
+ if (clone.border_bottom_fill && !clone.border_bottom) {
326
+ clone.border_bottom_fill = undefined;
327
+ }
328
+ if (clone.border_top_fill && !clone.border_top) {
329
+ clone.border_top_fill = undefined;
330
+ }
331
+ if (clone.border_left_fill && !clone.border_left) {
332
+ clone.border_left_fill = undefined;
333
+ }
334
+ if (clone.border_right_fill && !clone.border_right) {
335
+ clone.border_right_fill = undefined;
336
+ }
337
+
338
+ return JSON.stringify(clone);
339
+ },
340
+
321
341
  /**
322
342
  * merge. returns a new object, does not update dest in place.
323
343
  * NOTE: if it does not update dest in place, then what would be
@@ -48,6 +48,7 @@ import { ComplexFunctionLibrary } from './functions/complex-functions';
48
48
  import { MatrixFunctionLibrary } from './functions/matrix-functions';
49
49
  import { RegexFunctionLibrary } from './functions/regex-functions';
50
50
  import { LambdaFunctionLibrary } from './functions/lambda-functions';
51
+ import { FPFunctionLibrary } from './functions/fp';
51
52
 
52
53
  import { Variance } from './functions/statistics-functions';
53
54
 
@@ -242,6 +243,7 @@ export class Calculator extends Graph {
242
243
  MatrixFunctionLibrary,
243
244
  RegexFunctionLibrary,
244
245
  LambdaFunctionLibrary,
246
+ FPFunctionLibrary,
245
247
  );
246
248
 
247
249
  // aliases
@@ -19,7 +19,7 @@
19
19
  *
20
20
  */
21
21
 
22
- import type { RenderFunction, ClickFunction, UnionValue, ICellAddress, IArea } from 'treb-base-types';
22
+ import type { RenderFunction, ClickFunction, UnionValue, ICellAddress, IArea, FunctionUnion } from 'treb-base-types';
23
23
  import type { ExpressionUnit } from 'treb-parser';
24
24
 
25
25
  /**
@@ -28,6 +28,9 @@ import type { ExpressionUnit } from 'treb-parser';
28
28
  export interface FunctionContext {
29
29
  address: ICellAddress;
30
30
  area?: IArea;
31
+
32
+ /** application function for fp functions */
33
+ apply?: (fn: FunctionUnion, args: UnionValue[]) => UnionValue;
31
34
  }
32
35
 
33
36
  // FIXME: at least some of this could move to base types
@@ -235,6 +238,9 @@ export interface CompositeFunctionDescriptor {
235
238
  descriptors: ArgumentDescriptor[];
236
239
  }) => ContextResult | undefined;
237
240
 
241
+ /** flag indicating this function needs fp support */
242
+ fp?: boolean;
243
+
238
244
  }
239
245
 
240
246
  export interface FunctionMap {
@@ -27,11 +27,13 @@ import type { Cell, ICellAddress,
27
27
  UndefinedUnion,
28
28
  ComplexUnion,
29
29
  DimensionedQuantityUnion,
30
- IArea} from 'treb-base-types';
30
+ IArea,
31
+ FunctionUnion} from 'treb-base-types';
31
32
  import { ValueType, GetValueType, Area } from 'treb-base-types';
32
33
  import type { Parser, ExpressionUnit, UnitBinary, UnitIdentifier,
33
34
  UnitGroup, UnitUnary, UnitAddress, UnitRange, UnitCall, UnitDimensionedQuantity, UnitStructuredReference,
34
- UnitImplicitCall} from 'treb-parser';
35
+ UnitImplicitCall,
36
+ UnitArray} from 'treb-parser';
35
37
  import type { DataModel, MacroFunction, Sheet } from 'treb-data-model';
36
38
  import { NameError, ReferenceError, ExpressionError, UnknownError, SpillError, ValueError, ArgumentError } from './function-error';
37
39
 
@@ -448,6 +450,123 @@ export class ExpressionCalculator {
448
450
 
449
451
  }
450
452
 
453
+ /**
454
+ * split out from ImplicitCall so we can reuse
455
+ */
456
+ public ImplicitCallTail(result: FunctionUnion, args: ExpressionUnit[]) {
457
+
458
+ const value = result.value as {
459
+ bindings: ExpressionUnit[];
460
+ func: ExpressionUnit|undefined;
461
+ };
462
+
463
+ if (!value.func || !value.bindings) {
464
+ return ExpressionError();
465
+ }
466
+
467
+ // let frame = value.bindings(expr.args);
468
+
469
+ const frame: BindingFrame = {};
470
+
471
+ for (let i = 0; i < value.bindings.length; i++) {
472
+ const name = value.bindings[i];
473
+ if (name?.type === 'identifier') {
474
+ frame[name.name.toUpperCase()] = args[i] || { type: 'missing' };
475
+ }
476
+ else {
477
+ // should not happen, error
478
+ return ExpressionError();
479
+ }
480
+ }
481
+
482
+ // frame = this.NormalizeBindings(frame); // inline
483
+
484
+ const munged = JSON.parse(JSON.stringify(value.func));
485
+
486
+ this.parser.Walk2(munged, (unit: ExpressionUnit) => {
487
+ if (unit.type === 'identifier') {
488
+ const upper_case = unit.name.toUpperCase();
489
+ const binding = frame[upper_case];
490
+ if (binding) {
491
+ return JSON.parse(JSON.stringify(binding));
492
+ }
493
+ }
494
+ return true;
495
+ });
496
+
497
+ // console.info({frame, func, munged});
498
+
499
+ // const exec_result = this.CalculateExpression(munged as ExtendedExpressionUnit);
500
+ // return exec_result || ExpressionError();
501
+
502
+ return this.CalculateExpression(munged as ExtendedExpressionUnit);
503
+
504
+ }
505
+
506
+ /**
507
+ * an FP call will need to set bindings and call an expression,
508
+ * possibly multiple times. this is a support function for that.
509
+ */
510
+ protected Apply(fn: FunctionUnion, args: UnionValue[]) {
511
+
512
+ // kind of going backwards here, converting values to expressions...
513
+ // the reason is that we rewrite lambdas to support recursion
514
+
515
+ const mapped: ExpressionUnit[] = args.map(arg => {
516
+ switch (arg.type) {
517
+ case ValueType.number:
518
+ case ValueType.boolean:
519
+ case ValueType.string:
520
+ return {
521
+ type: 'literal', value: arg.value, id: 0, position: 0,
522
+ }
523
+
524
+ case ValueType.error:
525
+ return {
526
+ type: 'literal', value: '#' + arg.value, id: 0, position: 0,
527
+ }
528
+
529
+ case ValueType.array:
530
+ {
531
+ const values: UnitArray['values'] = [];
532
+ for (let c = 0; c < arg.value.length; c++) {
533
+ const col = arg.value[c];
534
+ const mapped_col: UnitArray['values'][0] = [];
535
+ for (let r = 0; r < col.length; r++ ) {
536
+ const val = col[r];
537
+ switch (val.type) {
538
+ case ValueType.boolean:
539
+ case ValueType.number:
540
+ case ValueType.string:
541
+ mapped_col.push(val.value || undefined);
542
+ break;
543
+ default:
544
+ console.warn('unhandled array value', val);
545
+ mapped_col.push(undefined);
546
+ }
547
+ }
548
+ values.push(mapped_col);
549
+ }
550
+
551
+ return {
552
+ type: 'array',
553
+ values,
554
+ id: 0, position: 0,
555
+ };
556
+ }
557
+
558
+ default:
559
+ console.warn('unhandled parameter value', arg);
560
+
561
+ }
562
+ return { type: 'missing', id: 0 };
563
+
564
+ });
565
+
566
+ return this.ImplicitCallTail(fn, mapped);
567
+
568
+ }
569
+
451
570
  /**
452
571
  * this method can take a `call` type if it looked like a call at
453
572
  * the parsing stage, it's a simple translation between the two
@@ -473,53 +592,7 @@ export class ExpressionCalculator {
473
592
  const result = this.CalculateExpression(expr.call as ExtendedExpressionUnit);
474
593
 
475
594
  if (result.type === ValueType.function) {
476
-
477
- const value = result.value as {
478
- bindings: ExpressionUnit[];
479
- func: ExpressionUnit|undefined;
480
- };
481
-
482
- if (!value.func || !value.bindings) {
483
- return ExpressionError();
484
- }
485
-
486
- // let frame = value.bindings(expr.args);
487
-
488
- const frame: BindingFrame = {};
489
-
490
- for (let i = 0; i < value.bindings.length; i++) {
491
- const name = value.bindings[i];
492
- if (name?.type === 'identifier') {
493
- frame[name.name.toUpperCase()] = expr.args[i] || { type: 'missing' };
494
- }
495
- else {
496
- // should not happen, error
497
- return ExpressionError();
498
- }
499
- }
500
-
501
- // frame = this.NormalizeBindings(frame); // inline
502
-
503
- const munged = JSON.parse(JSON.stringify(value.func));
504
-
505
- this.parser.Walk2(munged, (unit: ExpressionUnit) => {
506
- if (unit.type === 'identifier') {
507
- const upper_case = unit.name.toUpperCase();
508
- const binding = frame[upper_case];
509
- if (binding) {
510
- return JSON.parse(JSON.stringify(binding));
511
- }
512
- }
513
- return true;
514
- });
515
-
516
- // console.info({frame, func, munged});
517
-
518
- // const exec_result = this.CalculateExpression(munged as ExtendedExpressionUnit);
519
- // return exec_result || ExpressionError();
520
-
521
- return this.CalculateExpression(munged as ExtendedExpressionUnit);
522
-
595
+ return this.ImplicitCallTail(result, expr.args)
523
596
  }
524
597
 
525
598
  return ExpressionError();
@@ -721,11 +794,12 @@ export class ExpressionCalculator {
721
794
  start: { ...this.context.area.start, },
722
795
  end: { ...this.context.area.end, },
723
796
  } : undefined,
797
+ apply: func.fp ? this.Apply.bind(this) : undefined,
724
798
  };
725
799
 
726
- if (func.return_type === 'reference') {
800
+ const result = func.fn.apply(ctx, mapped_args);
727
801
 
728
- const result = func.fn.apply(ctx, mapped_args);
802
+ if (func.return_type === 'reference') {
729
803
 
730
804
  if (return_reference) {
731
805
  return result;
@@ -744,7 +818,7 @@ export class ExpressionCalculator {
744
818
 
745
819
  }
746
820
 
747
- return func.fn.apply(ctx, mapped_args);
821
+ return result; // func.fn.apply(ctx, mapped_args);
748
822
 
749
823
  };
750
824
 
@@ -1162,11 +1236,11 @@ export class ExpressionCalculator {
1162
1236
 
1163
1237
  switch (upper_case){
1164
1238
  case 'FALSE':
1165
- case 'F':
1239
+ // case 'F':
1166
1240
  return () => {return {value: false, type: ValueType.boolean}};
1167
1241
 
1168
1242
  case 'TRUE':
1169
- case 'T':
1243
+ // case 'T':
1170
1244
  return () => {return {value: true, type: ValueType.boolean}};
1171
1245
 
1172
1246
  case 'UNDEFINED':
@@ -1215,6 +1289,7 @@ export class ExpressionCalculator {
1215
1289
  return value;
1216
1290
  }
1217
1291
  }
1292
+
1218
1293
  return undefined;
1219
1294
  }
1220
1295
 
@@ -2767,6 +2767,7 @@ const block_list = [
2767
2767
  'imul',
2768
2768
  'clz32',
2769
2769
  'fround',
2770
+ 'f16round',
2770
2771
  ];
2771
2772
 
2772
2773
  const block_map: Record<string, string> = {};
@@ -0,0 +1,339 @@
1
+ /**
2
+ * functional programming
3
+ */
4
+
5
+ import type { FunctionMap } from '../descriptors';
6
+ import type { FunctionUnion, UnionValue} from 'treb-base-types';
7
+ import { ValueType } from 'treb-base-types';
8
+ import { ArgumentError, ValueError } from '../function-error';
9
+
10
+ export const FPFunctionLibrary: FunctionMap = {
11
+
12
+ MakeArray: {
13
+ description: 'Create an array using a function',
14
+ arguments: [
15
+ { name: 'rows' },
16
+ { name: 'columns' },
17
+ {
18
+ name: 'lambda',
19
+ description: 'Function to apply',
20
+ boxed: true,
21
+ },
22
+ ],
23
+ fp: true,
24
+ fn: function(rows: number, columns: number, lambda: FunctionUnion) {
25
+ if (rows > 0 && columns > 0 && lambda?.type === ValueType.function) {
26
+ const apply = this?.apply;
27
+ if (!apply) {
28
+ return ValueError();
29
+ }
30
+
31
+ const value: UnionValue[][] = [];
32
+ for (let c = 0; c < columns; c++) {
33
+ const values_col: UnionValue[] = [];
34
+ for (let r = 0; r < rows; r++) {
35
+ values_col.push(apply(lambda, [
36
+ { type: ValueType.number, value: r + 1 },
37
+ { type: ValueType.number, value: c + 1 },
38
+ ]));
39
+ }
40
+ value.push(values_col);
41
+ }
42
+
43
+ return {
44
+ type: ValueType.array,
45
+ value,
46
+ };
47
+
48
+ }
49
+ return ArgumentError();
50
+ },
51
+ },
52
+
53
+ Reduce: {
54
+ description: 'Accumulates a value by applying a function to a set of values',
55
+ arguments: [
56
+ {
57
+ name: 'initial value',
58
+ boxed: true,
59
+ },
60
+ {
61
+ name: 'data',
62
+ description: 'Input data',
63
+ boxed: true,
64
+ },
65
+ {
66
+ name: 'lambda',
67
+ description: 'Function to apply',
68
+ boxed: true,
69
+ },
70
+ ],
71
+ fp: true,
72
+ fn: function(initial: UnionValue, data: UnionValue, lambda: FunctionUnion) {
73
+
74
+ if (!this?.apply) {
75
+ return ValueError();
76
+ }
77
+
78
+ if (lambda.type !== ValueType.function) {
79
+ return ArgumentError();
80
+ }
81
+
82
+ if (data.type !== ValueType.array) {
83
+ data = {
84
+ type: ValueType.array,
85
+ value: [[data]],
86
+ }
87
+ }
88
+
89
+ for (let r = 0; r < data.value.length; r++) {
90
+ const row = data.value[r];
91
+ for (let c = 0; c < row.length; c++) {
92
+ const apply_args: UnionValue[] = [initial, row[c]];
93
+ const result = this.apply(lambda, apply_args)
94
+ initial = result;
95
+ }
96
+ }
97
+
98
+ return initial;
99
+
100
+ },
101
+ },
102
+
103
+ Scan: {
104
+ description: 'Applies a function to a set of values, iteratively',
105
+ arguments: [
106
+ {
107
+ name: 'initial value',
108
+ boxed: true,
109
+ },
110
+ {
111
+ name: 'data',
112
+ description: 'Input data',
113
+ boxed: true,
114
+ },
115
+ {
116
+ name: 'lambda',
117
+ description: 'Function to apply',
118
+ boxed: true,
119
+ },
120
+ ],
121
+ fp: true,
122
+ fn: function(initial: UnionValue, data: UnionValue, lambda: FunctionUnion) {
123
+
124
+ if (!this?.apply) {
125
+ return ValueError();
126
+ }
127
+
128
+ if (lambda.type !== ValueType.function) {
129
+ return ArgumentError();
130
+ }
131
+
132
+ if (data.type !== ValueType.array) {
133
+ data = {
134
+ type: ValueType.array,
135
+ value: [[data]],
136
+ }
137
+ }
138
+
139
+ const results: UnionValue[][] = [];
140
+ for (let r = 0; r < data.value.length; r++) {
141
+ const row = data.value[r];
142
+ const results_row: UnionValue[] = [];
143
+ for (let c = 0; c < row.length; c++) {
144
+ const apply_args: UnionValue[] = [initial, row[c]];
145
+ const result = this.apply(lambda, apply_args)
146
+ results_row.push(result);
147
+ initial = result;
148
+ }
149
+ results.push(results_row);
150
+ }
151
+
152
+ return {
153
+ type: ValueType.array,
154
+ value: results,
155
+ }
156
+
157
+ },
158
+ },
159
+
160
+ ByRow: {
161
+ description: 'Apply a function to each row in an array',
162
+ arguments: [
163
+ {
164
+ name: 'data',
165
+ description: 'Input data',
166
+ repeat: true,
167
+ boxed: true,
168
+ },
169
+ {
170
+ name: 'lambda',
171
+ description: 'Function to apply',
172
+ boxed: true,
173
+ },
174
+ ],
175
+ fp: true,
176
+ fn: function(data: UnionValue, lambda: FunctionUnion) {
177
+ if (!this?.apply) { return ValueError(); }
178
+
179
+ if (lambda.type !== ValueType.function) {
180
+ return ArgumentError();
181
+ }
182
+
183
+ if (data.type !== ValueType.array) {
184
+ data = {
185
+ type: ValueType.array,
186
+ value: [[data]],
187
+ };
188
+ }
189
+
190
+ const cols = data.value.length;
191
+ const rows = data.value[0].length;
192
+
193
+ const value: UnionValue[][] = [[]];
194
+ for (let r = 0; r < rows; r++) {
195
+ const args: UnionValue[] = [];
196
+ for (let c = 0; c < cols; c++) {
197
+ args.push(data.value[c][r]);
198
+ }
199
+ value[0].push(this.apply(lambda, [{
200
+ type: ValueType.array, value: [args],
201
+ }]));
202
+ }
203
+
204
+ return {
205
+ type: ValueType.array,
206
+ value,
207
+ }
208
+
209
+ }
210
+ },
211
+
212
+
213
+ ByCol: {
214
+ description: 'Apply a function to each column in an array',
215
+ arguments: [
216
+ {
217
+ name: 'data',
218
+ description: 'Input data',
219
+ repeat: true,
220
+ boxed: true,
221
+ },
222
+ {
223
+ name: 'lambda',
224
+ description: 'Function to apply',
225
+ boxed: true,
226
+ },
227
+ ],
228
+ fp: true,
229
+ fn: function(data: UnionValue, lambda: FunctionUnion) {
230
+ if (!this?.apply) { return ValueError(); }
231
+
232
+ if (lambda.type !== ValueType.function) {
233
+ return ArgumentError();
234
+ }
235
+
236
+ if (data.type !== ValueType.array) {
237
+ data = {
238
+ type: ValueType.array,
239
+ value: [[data]],
240
+ };
241
+ }
242
+
243
+ const cols = data.value.length;
244
+ const rows = data.value[0].length;
245
+
246
+ const value: UnionValue[][] = [];
247
+ for (let c = 0; c < cols; c++) {
248
+ const args: UnionValue[] = [];
249
+ for (let r = 0; r < rows; r++) {
250
+ args.push(data.value[c][r]);
251
+ }
252
+ value.push([this.apply(lambda, [{
253
+ type: ValueType.array, value: [args],
254
+ }])]);
255
+ }
256
+
257
+ return {
258
+ type: ValueType.array,
259
+ value,
260
+ }
261
+
262
+ }
263
+ },
264
+
265
+ Map: {
266
+ description: 'Apply a function to a set of values',
267
+ arguments: [
268
+ {
269
+ name: 'data',
270
+ description: 'Input data',
271
+ repeat: true,
272
+ boxed: true,
273
+ },
274
+ {
275
+ name: 'lambda',
276
+ description: 'Function to apply',
277
+ boxed: true,
278
+ },
279
+ ],
280
+
281
+ fp: true,
282
+ fn: function(...args: UnionValue[]) {
283
+
284
+ if (args.length < 2) {
285
+ return ArgumentError();
286
+ }
287
+
288
+ const lambda = args[args.length - 1];
289
+ if (lambda.type !== ValueType.function) {
290
+ return ArgumentError();
291
+ }
292
+
293
+ const apply = this?.apply;
294
+ if (!apply) {
295
+ return ValueError();
296
+ }
297
+
298
+ for (let i = 0; i < args.length - 1; i++) {
299
+ const arg = args[i];
300
+ if (arg.type !== ValueType.array) {
301
+ args[i] = {
302
+ type: ValueType.array,
303
+ value: [[arg]],
304
+ };
305
+ }
306
+ }
307
+
308
+ const key = args[0];
309
+ const results: UnionValue[][] = [];
310
+ if (key.type === ValueType.array) {
311
+ for (let r = 0; r < key.value.length; r++) {
312
+ const row = key.value[r];
313
+ const results_row: UnionValue[] = [];
314
+ for (let c = 0; c < row.length; c++) {
315
+ const apply_args: UnionValue[] = [row[c]];
316
+
317
+ for (let i = 1; i < args.length - 1; i++) {
318
+ const arg = args[i];
319
+ if (arg.type === ValueType.array) {
320
+ apply_args.push(arg.value[r][c])
321
+ }
322
+ }
323
+
324
+ results_row.push(apply(lambda, apply_args));
325
+ }
326
+ results.push(results_row);
327
+ }
328
+ }
329
+
330
+ return {
331
+ type: ValueType.array,
332
+ value: results,
333
+ };
334
+
335
+ },
336
+
337
+ },
338
+
339
+ };