@trebco/treb 32.1.1 → 32.3.3

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.3",
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,133 @@ 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
+ case ValueType.undefined:
559
+ return {
560
+ type: 'missing', id: 0,
561
+ };
562
+
563
+ default:
564
+
565
+ // this (logging) is a problem in a simulation because
566
+ // it can bog down. we probably should only log once.
567
+ // or perhaps not at all?
568
+
569
+ console.warn('unhandled parameter value', arg);
570
+
571
+ }
572
+ return { type: 'missing', id: 0 };
573
+
574
+ });
575
+
576
+ return this.ImplicitCallTail(fn, mapped);
577
+
578
+ }
579
+
451
580
  /**
452
581
  * this method can take a `call` type if it looked like a call at
453
582
  * the parsing stage, it's a simple translation between the two
@@ -473,53 +602,7 @@ export class ExpressionCalculator {
473
602
  const result = this.CalculateExpression(expr.call as ExtendedExpressionUnit);
474
603
 
475
604
  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
-
605
+ return this.ImplicitCallTail(result, expr.args)
523
606
  }
524
607
 
525
608
  return ExpressionError();
@@ -721,11 +804,12 @@ export class ExpressionCalculator {
721
804
  start: { ...this.context.area.start, },
722
805
  end: { ...this.context.area.end, },
723
806
  } : undefined,
807
+ apply: func.fp ? this.Apply.bind(this) : undefined,
724
808
  };
725
809
 
726
- if (func.return_type === 'reference') {
810
+ const result = func.fn.apply(ctx, mapped_args);
727
811
 
728
- const result = func.fn.apply(ctx, mapped_args);
812
+ if (func.return_type === 'reference') {
729
813
 
730
814
  if (return_reference) {
731
815
  return result;
@@ -744,7 +828,7 @@ export class ExpressionCalculator {
744
828
 
745
829
  }
746
830
 
747
- return func.fn.apply(ctx, mapped_args);
831
+ return result; // func.fn.apply(ctx, mapped_args);
748
832
 
749
833
  };
750
834
 
@@ -1162,11 +1246,11 @@ export class ExpressionCalculator {
1162
1246
 
1163
1247
  switch (upper_case){
1164
1248
  case 'FALSE':
1165
- case 'F':
1249
+ // case 'F':
1166
1250
  return () => {return {value: false, type: ValueType.boolean}};
1167
1251
 
1168
1252
  case 'TRUE':
1169
- case 'T':
1253
+ // case 'T':
1170
1254
  return () => {return {value: true, type: ValueType.boolean}};
1171
1255
 
1172
1256
  case 'UNDEFINED':
@@ -1215,6 +1299,7 @@ export class ExpressionCalculator {
1215
1299
  return value;
1216
1300
  }
1217
1301
  }
1302
+
1218
1303
  return undefined;
1219
1304
  }
1220
1305
 
@@ -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,360 @@
1
+ /*
2
+ * This file is part of TREB.
3
+ *
4
+ * TREB is free software: you can redistribute it and/or modify it under the
5
+ * terms of the GNU General Public License as published by the Free Software
6
+ * Foundation, either version 3 of the License, or (at your option) any
7
+ * later version.
8
+ *
9
+ * TREB is distributed in the hope that it will be useful, but WITHOUT ANY
10
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12
+ * details.
13
+ *
14
+ * You should have received a copy of the GNU General Public License along
15
+ * with TREB. If not, see <https://www.gnu.org/licenses/>.
16
+ *
17
+ * Copyright 2022-2024 trebco, llc.
18
+ * info@treb.app
19
+ *
20
+ */
21
+
22
+ import type { FunctionMap } from '../descriptors';
23
+ import type { FunctionUnion, UnionValue} from 'treb-base-types';
24
+ import { ValueType } from 'treb-base-types';
25
+ import { ArgumentError, ValueError } from '../function-error';
26
+
27
+ export const FPFunctionLibrary: FunctionMap = {
28
+
29
+ MakeArray: {
30
+ description: 'Create an array using a function',
31
+ arguments: [
32
+ { name: 'rows' },
33
+ { name: 'columns' },
34
+ {
35
+ name: 'lambda',
36
+ description: 'Function to apply',
37
+ boxed: true,
38
+ },
39
+ ],
40
+ fp: true,
41
+ fn: function(rows: number, columns: number, lambda: FunctionUnion) {
42
+ if (rows > 0 && columns > 0 && lambda?.type === ValueType.function) {
43
+ const apply = this?.apply;
44
+ if (!apply) {
45
+ return ValueError();
46
+ }
47
+
48
+ const value: UnionValue[][] = [];
49
+ for (let c = 0; c < columns; c++) {
50
+ const values_col: UnionValue[] = [];
51
+ for (let r = 0; r < rows; r++) {
52
+ values_col.push(apply(lambda, [
53
+ { type: ValueType.number, value: r + 1 },
54
+ { type: ValueType.number, value: c + 1 },
55
+ ]));
56
+ }
57
+ value.push(values_col);
58
+ }
59
+
60
+ return {
61
+ type: ValueType.array,
62
+ value,
63
+ };
64
+
65
+ }
66
+ return ArgumentError();
67
+ },
68
+ },
69
+
70
+ Reduce: {
71
+ description: 'Accumulates a value by applying a function to a set of values',
72
+ arguments: [
73
+ {
74
+ name: 'initial value',
75
+ boxed: true,
76
+ },
77
+ {
78
+ name: 'data',
79
+ description: 'Input data',
80
+ boxed: true,
81
+ },
82
+ {
83
+ name: 'lambda',
84
+ description: 'Function to apply',
85
+ boxed: true,
86
+ },
87
+ ],
88
+ fp: true,
89
+ fn: function(initial: UnionValue, data: UnionValue, lambda: FunctionUnion) {
90
+
91
+ if (!this?.apply) {
92
+ return ValueError();
93
+ }
94
+
95
+ if (lambda.type !== ValueType.function) {
96
+ return ArgumentError();
97
+ }
98
+
99
+ if (data.type !== ValueType.array) {
100
+ data = {
101
+ type: ValueType.array,
102
+ value: [[data]],
103
+ }
104
+ }
105
+
106
+ const cols = data.value.length;
107
+ const rows = data.value[0].length;
108
+
109
+ for (let r = 0; r < rows; r++) {
110
+ for (let c = 0; c < cols; c++) {
111
+ const apply_args: UnionValue[] = [initial, data.value[c][r]];
112
+ const result = this.apply(lambda, apply_args)
113
+ initial = result;
114
+ }
115
+ }
116
+
117
+ return initial;
118
+
119
+ },
120
+ },
121
+
122
+ Scan: {
123
+ description: 'Applies a function to a set of values, iteratively',
124
+ arguments: [
125
+ {
126
+ name: 'initial value',
127
+ boxed: true,
128
+ },
129
+ {
130
+ name: 'data',
131
+ description: 'Input data',
132
+ boxed: true,
133
+ },
134
+ {
135
+ name: 'lambda',
136
+ description: 'Function to apply',
137
+ boxed: true,
138
+ },
139
+ ],
140
+ fp: true,
141
+ fn: function(initial: UnionValue, data: UnionValue, lambda: FunctionUnion) {
142
+
143
+ if (!this?.apply) {
144
+ return ValueError();
145
+ }
146
+
147
+ if (lambda.type !== ValueType.function) {
148
+ return ArgumentError();
149
+ }
150
+
151
+ if (data.type !== ValueType.array) {
152
+ data = {
153
+ type: ValueType.array,
154
+ value: [[data]],
155
+ }
156
+ }
157
+
158
+ const results: UnionValue[][] = [];
159
+ const cols = data.value.length;
160
+ const rows = data.value[0].length;
161
+
162
+ for (let i = 0; i < cols; i++) { results.push([])}
163
+
164
+ for (let r = 0; r < rows; r++) {
165
+ for (let c = 0; c < cols; c++) {
166
+ const apply_args: UnionValue[] = [initial, data.value[c][r]];
167
+ const result = this.apply(lambda, apply_args)
168
+ results[c][r] = result;
169
+ initial = result;
170
+ }
171
+ }
172
+
173
+ return {
174
+ type: ValueType.array,
175
+ value: results,
176
+ }
177
+
178
+ },
179
+ },
180
+
181
+ ByRow: {
182
+ description: 'Apply a function to each row in an array',
183
+ arguments: [
184
+ {
185
+ name: 'data',
186
+ description: 'Input data',
187
+ repeat: true,
188
+ boxed: true,
189
+ },
190
+ {
191
+ name: 'lambda',
192
+ description: 'Function to apply',
193
+ boxed: true,
194
+ },
195
+ ],
196
+ fp: true,
197
+ fn: function(data: UnionValue, lambda: FunctionUnion) {
198
+ if (!this?.apply) { return ValueError(); }
199
+
200
+ if (lambda.type !== ValueType.function) {
201
+ return ArgumentError();
202
+ }
203
+
204
+ if (data.type !== ValueType.array) {
205
+ data = {
206
+ type: ValueType.array,
207
+ value: [[data]],
208
+ };
209
+ }
210
+
211
+ const cols = data.value.length;
212
+ const rows = data.value[0].length;
213
+
214
+ const value: UnionValue[][] = [[]];
215
+ for (let r = 0; r < rows; r++) {
216
+ const args: UnionValue[] = [];
217
+ for (let c = 0; c < cols; c++) {
218
+ args.push(data.value[c][r]);
219
+ }
220
+ value[0].push(this.apply(lambda, [{
221
+ type: ValueType.array, value: [args],
222
+ }]));
223
+ }
224
+
225
+ return {
226
+ type: ValueType.array,
227
+ value,
228
+ }
229
+
230
+ }
231
+ },
232
+
233
+
234
+ ByCol: {
235
+ description: 'Apply a function to each column in an array',
236
+ arguments: [
237
+ {
238
+ name: 'data',
239
+ description: 'Input data',
240
+ repeat: true,
241
+ boxed: true,
242
+ },
243
+ {
244
+ name: 'lambda',
245
+ description: 'Function to apply',
246
+ boxed: true,
247
+ },
248
+ ],
249
+ fp: true,
250
+ fn: function(data: UnionValue, lambda: FunctionUnion) {
251
+ if (!this?.apply) { return ValueError(); }
252
+
253
+ if (lambda.type !== ValueType.function) {
254
+ return ArgumentError();
255
+ }
256
+
257
+ if (data.type !== ValueType.array) {
258
+ data = {
259
+ type: ValueType.array,
260
+ value: [[data]],
261
+ };
262
+ }
263
+
264
+ const cols = data.value.length;
265
+ const rows = data.value[0].length;
266
+
267
+ const value: UnionValue[][] = [];
268
+ for (let c = 0; c < cols; c++) {
269
+ const args: UnionValue[] = [];
270
+ for (let r = 0; r < rows; r++) {
271
+ args.push(data.value[c][r]);
272
+ }
273
+ value.push([this.apply(lambda, [{
274
+ type: ValueType.array, value: [args],
275
+ }])]);
276
+ }
277
+
278
+ return {
279
+ type: ValueType.array,
280
+ value,
281
+ }
282
+
283
+ }
284
+ },
285
+
286
+ Map: {
287
+ description: 'Apply a function to a set of values',
288
+ arguments: [
289
+ {
290
+ name: 'data',
291
+ description: 'Input data',
292
+ repeat: true,
293
+ boxed: true,
294
+ },
295
+ {
296
+ name: 'lambda',
297
+ description: 'Function to apply',
298
+ boxed: true,
299
+ },
300
+ ],
301
+
302
+ fp: true,
303
+ fn: function(...args: UnionValue[]) {
304
+
305
+ if (args.length < 2) {
306
+ return ArgumentError();
307
+ }
308
+
309
+ const lambda = args[args.length - 1];
310
+ if (lambda.type !== ValueType.function) {
311
+ return ArgumentError();
312
+ }
313
+
314
+ const apply = this?.apply;
315
+ if (!apply) {
316
+ return ValueError();
317
+ }
318
+
319
+ for (let i = 0; i < args.length - 1; i++) {
320
+ const arg = args[i];
321
+ if (arg.type !== ValueType.array) {
322
+ args[i] = {
323
+ type: ValueType.array,
324
+ value: [[arg]],
325
+ };
326
+ }
327
+ }
328
+
329
+ const key = args[0];
330
+ const results: UnionValue[][] = [];
331
+ if (key.type === ValueType.array) {
332
+ for (let r = 0; r < key.value.length; r++) {
333
+ const row = key.value[r];
334
+ const results_row: UnionValue[] = [];
335
+ for (let c = 0; c < row.length; c++) {
336
+ const apply_args: UnionValue[] = [row[c]];
337
+
338
+ for (let i = 1; i < args.length - 1; i++) {
339
+ const arg = args[i];
340
+ if (arg.type === ValueType.array) {
341
+ apply_args.push(arg.value[r][c])
342
+ }
343
+ }
344
+
345
+ results_row.push(apply(lambda, apply_args));
346
+ }
347
+ results.push(results_row);
348
+ }
349
+ }
350
+
351
+ return {
352
+ type: ValueType.array,
353
+ value: results,
354
+ };
355
+
356
+ },
357
+
358
+ },
359
+
360
+ };