@trebco/treb 23.6.5 → 25.0.0-rc2
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/.eslintignore +8 -0
- package/.eslintrc.js +164 -0
- package/README-shadow-DOM.md +88 -0
- package/README.md +37 -130
- package/api-config.json +29 -0
- package/api-generator/api-generator-types.ts +82 -0
- package/api-generator/api-generator.ts +1172 -0
- package/api-generator/package.json +3 -0
- package/build/treb-spreadsheet.mjs +14 -0
- package/{treb.d.ts → build/treb.d.ts} +323 -271
- package/esbuild-custom-element.mjs +336 -0
- package/esbuild.js +305 -0
- package/package.json +49 -14
- package/treb-base-types/package.json +5 -0
- package/treb-base-types/src/api_types.ts +36 -0
- package/treb-base-types/src/area.ts +583 -0
- package/treb-base-types/src/basic_types.ts +45 -0
- package/treb-base-types/src/cell.ts +612 -0
- package/treb-base-types/src/cells.ts +1066 -0
- package/treb-base-types/src/color.ts +124 -0
- package/treb-base-types/src/import.ts +71 -0
- package/treb-base-types/src/index-standalone.ts +29 -0
- package/treb-base-types/src/index.ts +42 -0
- package/treb-base-types/src/layout.ts +47 -0
- package/treb-base-types/src/localization.ts +187 -0
- package/treb-base-types/src/rectangle.ts +145 -0
- package/treb-base-types/src/render_text.ts +72 -0
- package/treb-base-types/src/style.ts +545 -0
- package/treb-base-types/src/table.ts +109 -0
- package/treb-base-types/src/text_part.ts +54 -0
- package/treb-base-types/src/theme.ts +608 -0
- package/treb-base-types/src/union.ts +152 -0
- package/treb-base-types/src/value-type.ts +164 -0
- package/treb-base-types/style/resizable.css +59 -0
- package/treb-calculator/modern.tsconfig.json +11 -0
- package/treb-calculator/package.json +5 -0
- package/treb-calculator/src/calculator.ts +2546 -0
- package/treb-calculator/src/complex-math.ts +558 -0
- package/treb-calculator/src/dag/array-vertex.ts +198 -0
- package/treb-calculator/src/dag/graph.ts +951 -0
- package/treb-calculator/src/dag/leaf_vertex.ts +118 -0
- package/treb-calculator/src/dag/spreadsheet_vertex.ts +327 -0
- package/treb-calculator/src/dag/spreadsheet_vertex_base.ts +44 -0
- package/treb-calculator/src/dag/vertex.ts +352 -0
- package/treb-calculator/src/descriptors.ts +162 -0
- package/treb-calculator/src/expression-calculator.ts +1069 -0
- package/treb-calculator/src/function-error.ts +103 -0
- package/treb-calculator/src/function-library.ts +103 -0
- package/treb-calculator/src/functions/base-functions.ts +1214 -0
- package/treb-calculator/src/functions/checkbox.ts +164 -0
- package/treb-calculator/src/functions/complex-functions.ts +253 -0
- package/treb-calculator/src/functions/finance-functions.ts +399 -0
- package/treb-calculator/src/functions/information-functions.ts +102 -0
- package/treb-calculator/src/functions/matrix-functions.ts +182 -0
- package/treb-calculator/src/functions/sparkline.ts +335 -0
- package/treb-calculator/src/functions/statistics-functions.ts +350 -0
- package/treb-calculator/src/functions/text-functions.ts +298 -0
- package/treb-calculator/src/index.ts +27 -0
- package/treb-calculator/src/notifier-types.ts +59 -0
- package/treb-calculator/src/primitives.ts +428 -0
- package/treb-calculator/src/utilities.ts +305 -0
- package/treb-charts/package.json +5 -0
- package/treb-charts/src/chart-functions.ts +156 -0
- package/treb-charts/src/chart-types.ts +230 -0
- package/treb-charts/src/chart.ts +1288 -0
- package/treb-charts/src/index.ts +24 -0
- package/treb-charts/src/main.ts +37 -0
- package/treb-charts/src/rectangle.ts +52 -0
- package/treb-charts/src/renderer.ts +1841 -0
- package/treb-charts/src/util.ts +122 -0
- package/treb-charts/style/charts.scss +221 -0
- package/treb-charts/style/old-charts.scss +250 -0
- package/treb-embed/markup/layout.html +137 -0
- package/treb-embed/markup/toolbar.html +175 -0
- package/treb-embed/modern.tsconfig.json +25 -0
- package/treb-embed/src/custom-element/content-types.d.ts +18 -0
- package/treb-embed/src/custom-element/global.d.ts +11 -0
- package/treb-embed/src/custom-element/spreadsheet-constructor.ts +1228 -0
- package/treb-embed/src/custom-element/treb-global.ts +44 -0
- package/treb-embed/src/custom-element/treb-spreadsheet-element.ts +52 -0
- package/treb-embed/src/embedded-spreadsheet.ts +5358 -0
- package/treb-embed/src/index.ts +16 -0
- package/treb-embed/src/language-model.ts +41 -0
- package/treb-embed/src/options.ts +298 -0
- package/treb-embed/src/progress-dialog.ts +228 -0
- package/treb-embed/src/selection-state.ts +16 -0
- package/treb-embed/src/spinner.ts +42 -0
- package/treb-embed/src/toolbar-message.ts +96 -0
- package/treb-embed/src/types.ts +167 -0
- package/treb-embed/style/autocomplete.scss +103 -0
- package/treb-embed/style/dark-theme.scss +114 -0
- package/treb-embed/style/defaults.scss +36 -0
- package/treb-embed/style/dialog.scss +181 -0
- package/treb-embed/style/dropdown-select.scss +101 -0
- package/treb-embed/style/formula-bar.scss +193 -0
- package/treb-embed/style/grid.scss +374 -0
- package/treb-embed/style/layout.scss +424 -0
- package/treb-embed/style/mouse-mask.scss +67 -0
- package/treb-embed/style/note.scss +92 -0
- package/treb-embed/style/overlay-editor.scss +102 -0
- package/treb-embed/style/spinner.scss +92 -0
- package/treb-embed/style/tab-bar.scss +228 -0
- package/treb-embed/style/table.scss +80 -0
- package/treb-embed/style/theme-defaults.scss +444 -0
- package/treb-embed/style/toolbar.scss +416 -0
- package/treb-embed/style/tooltip.scss +68 -0
- package/treb-embed/style/treb-icons.scss +130 -0
- package/treb-embed/style/treb-spreadsheet-element.scss +20 -0
- package/treb-embed/style/z-index.scss +43 -0
- package/treb-export/docs/charts.md +68 -0
- package/treb-export/modern.tsconfig.json +19 -0
- package/treb-export/package.json +4 -0
- package/treb-export/src/address-type.ts +77 -0
- package/treb-export/src/base-template.ts +22 -0
- package/treb-export/src/column-width.ts +85 -0
- package/treb-export/src/drawing2/chart-template-components2.ts +389 -0
- package/treb-export/src/drawing2/chart2.ts +282 -0
- package/treb-export/src/drawing2/column-chart-template2.ts +521 -0
- package/treb-export/src/drawing2/donut-chart-template2.ts +296 -0
- package/treb-export/src/drawing2/drawing2.ts +355 -0
- package/treb-export/src/drawing2/embedded-image.ts +71 -0
- package/treb-export/src/drawing2/scatter-chart-template2.ts +555 -0
- package/treb-export/src/export-worker/export-worker.ts +99 -0
- package/treb-export/src/export-worker/index-modern.ts +22 -0
- package/treb-export/src/export2.ts +2204 -0
- package/treb-export/src/import2.ts +882 -0
- package/treb-export/src/relationship.ts +36 -0
- package/treb-export/src/shared-strings2.ts +128 -0
- package/treb-export/src/template-2.ts +22 -0
- package/treb-export/src/unescape_xml.ts +47 -0
- package/treb-export/src/workbook-sheet2.ts +182 -0
- package/treb-export/src/workbook-style2.ts +1285 -0
- package/treb-export/src/workbook-theme2.ts +88 -0
- package/treb-export/src/workbook2.ts +491 -0
- package/treb-export/src/xml-utils.ts +201 -0
- package/treb-export/template/base/[Content_Types].xml +2 -0
- package/treb-export/template/base/_rels/.rels +2 -0
- package/treb-export/template/base/docProps/app.xml +2 -0
- package/treb-export/template/base/docProps/core.xml +12 -0
- package/treb-export/template/base/xl/_rels/workbook.xml.rels +2 -0
- package/treb-export/template/base/xl/sharedStrings.xml +2 -0
- package/treb-export/template/base/xl/styles.xml +2 -0
- package/treb-export/template/base/xl/theme/theme1.xml +2 -0
- package/treb-export/template/base/xl/workbook.xml +2 -0
- package/treb-export/template/base/xl/worksheets/sheet1.xml +2 -0
- package/treb-export/template/base.xlsx +0 -0
- package/treb-format/package.json +8 -0
- package/treb-format/src/format.test.ts +213 -0
- package/treb-format/src/format.ts +942 -0
- package/treb-format/src/format_cache.ts +199 -0
- package/treb-format/src/format_parser.ts +723 -0
- package/treb-format/src/index.ts +25 -0
- package/treb-format/src/number_format_section.ts +100 -0
- package/treb-format/src/value_parser.ts +337 -0
- package/treb-grid/package.json +5 -0
- package/treb-grid/src/editors/autocomplete.ts +394 -0
- package/treb-grid/src/editors/autocomplete_matcher.ts +260 -0
- package/treb-grid/src/editors/formula_bar.ts +473 -0
- package/treb-grid/src/editors/formula_editor_base.ts +910 -0
- package/treb-grid/src/editors/overlay_editor.ts +511 -0
- package/treb-grid/src/index.ts +37 -0
- package/treb-grid/src/layout/base_layout.ts +2618 -0
- package/treb-grid/src/layout/grid_layout.ts +299 -0
- package/treb-grid/src/layout/rectangle_cache.ts +86 -0
- package/treb-grid/src/render/selection-renderer.ts +414 -0
- package/treb-grid/src/render/svg_header_overlay.ts +93 -0
- package/treb-grid/src/render/svg_selection_block.ts +187 -0
- package/treb-grid/src/render/tile_renderer.ts +2122 -0
- package/treb-grid/src/types/annotation.ts +216 -0
- package/treb-grid/src/types/border_constants.ts +34 -0
- package/treb-grid/src/types/clipboard_data.ts +31 -0
- package/treb-grid/src/types/data_model.ts +334 -0
- package/treb-grid/src/types/drag_mask.ts +81 -0
- package/treb-grid/src/types/grid.ts +7743 -0
- package/treb-grid/src/types/grid_base.ts +3644 -0
- package/treb-grid/src/types/grid_command.ts +470 -0
- package/treb-grid/src/types/grid_events.ts +124 -0
- package/treb-grid/src/types/grid_options.ts +97 -0
- package/treb-grid/src/types/grid_selection.ts +60 -0
- package/treb-grid/src/types/named_range.ts +369 -0
- package/treb-grid/src/types/scale-control.ts +202 -0
- package/treb-grid/src/types/serialize_options.ts +72 -0
- package/treb-grid/src/types/set_range_options.ts +52 -0
- package/treb-grid/src/types/sheet.ts +3099 -0
- package/treb-grid/src/types/sheet_types.ts +95 -0
- package/treb-grid/src/types/tab_bar.ts +464 -0
- package/treb-grid/src/types/tile.ts +59 -0
- package/treb-grid/src/types/update_flags.ts +75 -0
- package/treb-grid/src/util/dom_utilities.ts +44 -0
- package/treb-grid/src/util/fontmetrics2.ts +179 -0
- package/treb-grid/src/util/ua.ts +104 -0
- package/treb-logo.svg +18 -0
- package/treb-parser/package.json +5 -0
- package/treb-parser/src/csv-parser.ts +122 -0
- package/treb-parser/src/index.ts +25 -0
- package/treb-parser/src/md-parser.ts +526 -0
- package/treb-parser/src/parser-types.ts +397 -0
- package/treb-parser/src/parser.test.ts +298 -0
- package/treb-parser/src/parser.ts +2673 -0
- package/treb-utils/package.json +5 -0
- package/treb-utils/src/dispatch.ts +57 -0
- package/treb-utils/src/event_source.ts +147 -0
- package/treb-utils/src/ievent_source.ts +33 -0
- package/treb-utils/src/index.ts +31 -0
- package/treb-utils/src/measurement.ts +174 -0
- package/treb-utils/src/resizable.ts +160 -0
- package/treb-utils/src/scale.ts +137 -0
- package/treb-utils/src/serialize_html.ts +124 -0
- package/treb-utils/src/template.ts +70 -0
- package/treb-utils/src/validate_uri.ts +61 -0
- package/tsconfig.json +10 -0
- package/tsproject.json +30 -0
- package/util/license-plugin-esbuild.js +86 -0
- package/util/list-css-vars.sh +46 -0
- package/README-esm.md +0 -37
- package/treb-bundle.css +0 -2
- package/treb-bundle.mjs +0 -15
|
@@ -0,0 +1,1069 @@
|
|
|
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-2023 trebco, llc.
|
|
18
|
+
* info@treb.app
|
|
19
|
+
*
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import type { FunctionLibrary } from './function-library';
|
|
23
|
+
import { Cell, ICellAddress, ValueType, GetValueType,
|
|
24
|
+
Area, UnionValue, CellValue,
|
|
25
|
+
ArrayUnion,
|
|
26
|
+
NumberUnion,
|
|
27
|
+
UndefinedUnion,
|
|
28
|
+
ComplexUnion } from 'treb-base-types';
|
|
29
|
+
import type { Parser, ExpressionUnit, UnitBinary, UnitIdentifier,
|
|
30
|
+
UnitGroup, UnitUnary, UnitAddress, UnitRange, UnitCall, UnitDimensionedQuantity, UnitStructuredReference } from 'treb-parser';
|
|
31
|
+
import type { DataModel, MacroFunction, Sheet } from 'treb-grid';
|
|
32
|
+
import { NameError, ReferenceError, ExpressionError, UnknownError } from './function-error';
|
|
33
|
+
import { ReturnType } from './descriptors';
|
|
34
|
+
|
|
35
|
+
import * as Primitives from './primitives';
|
|
36
|
+
|
|
37
|
+
export type ExtendedExpressionUnit = ExpressionUnit & { user_data: any }; // export for MC overload
|
|
38
|
+
|
|
39
|
+
// FIXME: move
|
|
40
|
+
export const UnionIsExpressionUnit = (test: UnionValue /*UnionOrArray*/): test is { type: ValueType.object, value: ExpressionUnit } => {
|
|
41
|
+
return !Array.isArray(test)
|
|
42
|
+
&& test.type === ValueType.object
|
|
43
|
+
&& (!!(test.value as ExpressionUnit).type);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// FIXME: move
|
|
47
|
+
export const UnionIsMetadata = (test: UnionValue /*UnionOrArray*/): test is { type: ValueType.object, value: ReferenceMetadata } => {
|
|
48
|
+
|
|
49
|
+
return test.type === ValueType.object && test.key === 'metadata';
|
|
50
|
+
|
|
51
|
+
/*
|
|
52
|
+
return !Array.isArray(test)
|
|
53
|
+
&& test.type === ValueType.object
|
|
54
|
+
&& ((test.value as ReferenceMetadata).type === 'metadata');
|
|
55
|
+
*/
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// FIXME: move
|
|
59
|
+
export interface ReferenceMetadata {
|
|
60
|
+
type: 'metadata';
|
|
61
|
+
address: UnitAddress; // ICellAddress;
|
|
62
|
+
value: CellValue;
|
|
63
|
+
format?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface CalculationContext {
|
|
67
|
+
address: ICellAddress;
|
|
68
|
+
model?: DataModel;
|
|
69
|
+
volatile: boolean;
|
|
70
|
+
call_index: number;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export class ExpressionCalculator {
|
|
74
|
+
|
|
75
|
+
public context: CalculationContext = {
|
|
76
|
+
address: { row: -1, column: -1 },
|
|
77
|
+
volatile: false,
|
|
78
|
+
call_index: 0,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* this refers to the number of function call within a single cell.
|
|
83
|
+
* so if you have a function like
|
|
84
|
+
*
|
|
85
|
+
* =A(B())
|
|
86
|
+
*
|
|
87
|
+
* then when calculating A call index should be set to 1; and when
|
|
88
|
+
* calculating B, call index is 2. and so on.
|
|
89
|
+
*/
|
|
90
|
+
protected call_index = 0;
|
|
91
|
+
|
|
92
|
+
// local reference
|
|
93
|
+
// protected cells: Cells = new Cells();
|
|
94
|
+
// protected cells_map: {[index: number]: Cells} = {};
|
|
95
|
+
// protected sheet_name_map: {[index: string]: number} = {};
|
|
96
|
+
|
|
97
|
+
// local reference
|
|
98
|
+
protected named_range_map: {[index: string]: Area} = {};
|
|
99
|
+
|
|
100
|
+
// protected bound_name_stack: Array<Record<string, ExpressionUnit>> = [];
|
|
101
|
+
|
|
102
|
+
//
|
|
103
|
+
protected data_model!: DataModel;
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
// --- public API -----------------------------------------------------------
|
|
107
|
+
|
|
108
|
+
constructor(
|
|
109
|
+
protected readonly library: FunctionLibrary,
|
|
110
|
+
protected readonly parser: Parser) {}
|
|
111
|
+
|
|
112
|
+
public SetModel(model: DataModel): void {
|
|
113
|
+
|
|
114
|
+
// this.cells_map = {};
|
|
115
|
+
// this.sheet_name_map = {};
|
|
116
|
+
|
|
117
|
+
/*
|
|
118
|
+
for (const sheet of model.sheets.list) {
|
|
119
|
+
// this.cells_map[sheet.id] = sheet.cells;
|
|
120
|
+
// this.sheet_name_map[sheet.name.toLowerCase()] = sheet.id;
|
|
121
|
+
}
|
|
122
|
+
*/
|
|
123
|
+
|
|
124
|
+
this.data_model = model;
|
|
125
|
+
this.named_range_map = model.named_ranges.Map();
|
|
126
|
+
this.context.model = model;
|
|
127
|
+
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* there's a case where we are calling this from within a function
|
|
132
|
+
* (which is weird, but hey) and to do that we need to preserve flags.
|
|
133
|
+
*/
|
|
134
|
+
public Calculate(expr: ExpressionUnit, addr: ICellAddress, preserve_flags = false): {
|
|
135
|
+
value: UnionValue /*UnionOrArray*/, volatile: boolean }{
|
|
136
|
+
|
|
137
|
+
if (!preserve_flags) {
|
|
138
|
+
|
|
139
|
+
this.context.address = addr;
|
|
140
|
+
this.context.volatile = false;
|
|
141
|
+
this.context.call_index = 0;
|
|
142
|
+
|
|
143
|
+
// reset for this cell
|
|
144
|
+
this.call_index = 0; // why not in model? A: timing (nested)
|
|
145
|
+
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
value: this.CalculateExpression(expr as ExtendedExpressionUnit),
|
|
150
|
+
volatile: this.context.volatile,
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// --- /public API ----------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* resolve value from cell. returns a function bound to specific cell.
|
|
159
|
+
*/
|
|
160
|
+
protected CellFunction2(expr: UnitAddress): () => UnionValue {
|
|
161
|
+
|
|
162
|
+
if (!expr.sheet_id) {
|
|
163
|
+
if (expr.sheet) {
|
|
164
|
+
expr.sheet_id = this.data_model.sheets.ID(expr.sheet) || 0;
|
|
165
|
+
|
|
166
|
+
// expr.sheet_id = this.sheet_name_map[expr.sheet.toLowerCase()];
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
return () => ReferenceError();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// const cells = this.cells_map[expr.sheet_id];
|
|
174
|
+
const cells = this.data_model.sheets.Find(expr.sheet_id)?.cells;
|
|
175
|
+
|
|
176
|
+
if (!cells) {
|
|
177
|
+
console.warn('missing cells reference @ ' + expr.sheet_id);
|
|
178
|
+
return () => ReferenceError();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// reference
|
|
182
|
+
const cell = cells.GetCell(expr);
|
|
183
|
+
|
|
184
|
+
// this is not an error, just a reference to an empty cell
|
|
185
|
+
// FIXME: should this be 0? probably
|
|
186
|
+
|
|
187
|
+
if (!cell) {
|
|
188
|
+
return () => {
|
|
189
|
+
// return { type: ValueType.undefined, value: undefined }
|
|
190
|
+
return { type: ValueType.number, value: 0 };
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// close
|
|
195
|
+
return () => cell.GetValue4();
|
|
196
|
+
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* returns range as union type. returns a single value for a single cell,
|
|
201
|
+
* or a 2d array (never a 1d array)
|
|
202
|
+
*/
|
|
203
|
+
protected CellFunction4(start: ICellAddress, end: ICellAddress): UnionValue /*UnionOrArray*/ {
|
|
204
|
+
|
|
205
|
+
if (!start.sheet_id) {
|
|
206
|
+
return ReferenceError();
|
|
207
|
+
// throw new Error('missing sheet id in CellFunction4');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
//const cells = this.cells_map[start.sheet_id];
|
|
211
|
+
const cells = this.data_model.sheets.Find(start.sheet_id)?.cells;
|
|
212
|
+
|
|
213
|
+
return cells?.GetRange4(start, end, true) || ReferenceError();
|
|
214
|
+
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** breaking this out to de-dupe */
|
|
218
|
+
protected GetMetadata(arg: ExpressionUnit, map_result: (cell_data: Cell, address: ICellAddress) => any): UnionValue /*UnionOrArray*/ {
|
|
219
|
+
|
|
220
|
+
// FIXME: we used to restrict this to non-cell functions, now
|
|
221
|
+
// we are using it for the cell function (we used to use address,
|
|
222
|
+
// which just returns the label)
|
|
223
|
+
|
|
224
|
+
let address: ICellAddress|undefined;
|
|
225
|
+
let range: {start: ICellAddress; end: ICellAddress} | undefined;
|
|
226
|
+
|
|
227
|
+
switch (arg.type) {
|
|
228
|
+
case 'address':
|
|
229
|
+
address = arg;
|
|
230
|
+
break;
|
|
231
|
+
|
|
232
|
+
case 'range':
|
|
233
|
+
range = arg;
|
|
234
|
+
break;
|
|
235
|
+
|
|
236
|
+
case 'structured-reference':
|
|
237
|
+
{
|
|
238
|
+
const resolved = this.data_model.ResolveStructuredReference(arg, this.context.address);
|
|
239
|
+
if (resolved) {
|
|
240
|
+
if (resolved.type === 'address') {
|
|
241
|
+
address = resolved;
|
|
242
|
+
}
|
|
243
|
+
else if (resolved.type === 'range') {
|
|
244
|
+
range = resolved;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
break;
|
|
249
|
+
|
|
250
|
+
case 'identifier':
|
|
251
|
+
{
|
|
252
|
+
const named_range = this.named_range_map[arg.name.toUpperCase()];
|
|
253
|
+
if (named_range) {
|
|
254
|
+
if (named_range.count === 1) {
|
|
255
|
+
address = named_range.start; // FIXME: range?
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
range = named_range;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
break;
|
|
263
|
+
|
|
264
|
+
case 'call':
|
|
265
|
+
|
|
266
|
+
// we need a way to cascade the 'metadata' flag down
|
|
267
|
+
// through calls so we can use indirect/offset addressing...
|
|
268
|
+
|
|
269
|
+
// at the same time you don't want to cascade down indefinitely,
|
|
270
|
+
// otherwise the function call itself won't work properly...
|
|
271
|
+
|
|
272
|
+
// [how to resolve?]
|
|
273
|
+
|
|
274
|
+
{
|
|
275
|
+
const result = this.CalculateExpression(arg as ExtendedExpressionUnit, true) as UnionValue /*UnionOrArray*/;
|
|
276
|
+
if (UnionIsExpressionUnit(result)) {
|
|
277
|
+
if (result.value.type === 'address') {
|
|
278
|
+
address = result.value;
|
|
279
|
+
}
|
|
280
|
+
else if (result.value.type === 'range') {
|
|
281
|
+
range = result.value;
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
return result;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
else return result;
|
|
288
|
+
}
|
|
289
|
+
break;
|
|
290
|
+
|
|
291
|
+
default:
|
|
292
|
+
return this.CalculateExpression(arg as ExtendedExpressionUnit); // as UnionOrArray;
|
|
293
|
+
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (address) {
|
|
297
|
+
|
|
298
|
+
// don't we have a map? [...] only for names?
|
|
299
|
+
|
|
300
|
+
let sheet: Sheet|undefined; // = this.data_model.active_sheet;
|
|
301
|
+
if (address.sheet_id) { // && address.sheet_id !== sheet.id) {
|
|
302
|
+
sheet = this.data_model.sheets.Find(address.sheet_id);
|
|
303
|
+
|
|
304
|
+
/*
|
|
305
|
+
for (const test of this.data_model.sheets) {
|
|
306
|
+
if (test.id === address.sheet_id) {
|
|
307
|
+
sheet = test;
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
*/
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (!sheet) {
|
|
315
|
+
// throw new Error('missing sheet [ac8]');
|
|
316
|
+
console.error('missing sheet [ac8]');
|
|
317
|
+
return ReferenceError();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const cell_data = sheet.CellData(address);
|
|
321
|
+
const value = // (cell_data.type === ValueType.formula) ? cell_data.calculated : cell_data.value;
|
|
322
|
+
cell_data.calculated_type ? cell_data.calculated : cell_data.value;
|
|
323
|
+
|
|
324
|
+
const metadata: ReferenceMetadata = {
|
|
325
|
+
type: 'metadata',
|
|
326
|
+
address: {...address},
|
|
327
|
+
value,
|
|
328
|
+
format: cell_data.style ? cell_data.style.number_format : undefined,
|
|
329
|
+
...map_result(cell_data, address),
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
return { type: ValueType.object, value: metadata, key: 'metadata' };
|
|
333
|
+
|
|
334
|
+
}
|
|
335
|
+
else if (range) {
|
|
336
|
+
|
|
337
|
+
if (range.start.row === Infinity || range.start.column === Infinity) {
|
|
338
|
+
return ReferenceError();
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
let sheet: Sheet|undefined; // = this.data_model.active_sheet;
|
|
342
|
+
if (range.start.sheet_id) { // && range.start.sheet_id !== sheet.id) {
|
|
343
|
+
sheet = this.data_model.sheets.Find(range.start.sheet_id);
|
|
344
|
+
/*
|
|
345
|
+
for (const test of this.data_model.sheets) {
|
|
346
|
+
if (test.id === range.start.sheet_id) {
|
|
347
|
+
sheet = test;
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
*/
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (!sheet) {
|
|
355
|
+
// console.info({range, context: JSON.stringify(this.context.address)});
|
|
356
|
+
// console.info({arg});
|
|
357
|
+
throw new Error('missing sheet [ac9]');
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const range_result: UnionValue[][] = [];
|
|
361
|
+
|
|
362
|
+
for (let column = range.start.column; column <= range.end.column; column++) {
|
|
363
|
+
const column_result: UnionValue[] = [];
|
|
364
|
+
for (let row = range.start.row; row <= range.end.row; row++) {
|
|
365
|
+
const cell_data = sheet.CellData({row, column});
|
|
366
|
+
address = {...range.start, row, column};
|
|
367
|
+
|
|
368
|
+
const value = // (cell_data.type === ValueType.formula) ? cell_data.calculated : cell_data.value;
|
|
369
|
+
cell_data.calculated_type ? cell_data.calculated : cell_data.value;
|
|
370
|
+
|
|
371
|
+
const metadata = {
|
|
372
|
+
type: 'metadata',
|
|
373
|
+
address,
|
|
374
|
+
value,
|
|
375
|
+
format: cell_data.style ? cell_data.style.number_format : undefined,
|
|
376
|
+
...map_result(cell_data, address),
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
column_result.push({
|
|
380
|
+
type: ValueType.object,
|
|
381
|
+
value: metadata,
|
|
382
|
+
key: 'metadata',
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
}
|
|
386
|
+
range_result.push(column_result);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return {type: ValueType.array, value: range_result};
|
|
390
|
+
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return this.CalculateExpression(arg as ExtendedExpressionUnit); /*UnionOrArray*/
|
|
394
|
+
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
protected RewriteMacro(
|
|
398
|
+
unit: ExpressionUnit,
|
|
399
|
+
names: Record<string, ExpressionUnit>,
|
|
400
|
+
): ExpressionUnit {
|
|
401
|
+
|
|
402
|
+
let expr: ExpressionUnit;
|
|
403
|
+
|
|
404
|
+
switch (unit.type) {
|
|
405
|
+
|
|
406
|
+
case 'identifier':
|
|
407
|
+
expr = names[unit.name.toUpperCase()];
|
|
408
|
+
if (expr) {
|
|
409
|
+
return JSON.parse(JSON.stringify(expr)) as ExpressionUnit;
|
|
410
|
+
}
|
|
411
|
+
break;
|
|
412
|
+
|
|
413
|
+
case 'binary':
|
|
414
|
+
unit.left = this.RewriteMacro(unit.left, names);
|
|
415
|
+
unit.right = this.RewriteMacro(unit.right, names);
|
|
416
|
+
break;
|
|
417
|
+
|
|
418
|
+
case 'unary':
|
|
419
|
+
unit.operand = this.RewriteMacro(unit.operand, names);
|
|
420
|
+
break;
|
|
421
|
+
|
|
422
|
+
case 'group':
|
|
423
|
+
unit.elements = unit.elements.map(element => this.RewriteMacro(element, names));
|
|
424
|
+
break;
|
|
425
|
+
|
|
426
|
+
case 'call':
|
|
427
|
+
unit.args = unit.args.map(arg => this.RewriteMacro(arg, names));
|
|
428
|
+
break;
|
|
429
|
+
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return unit;
|
|
433
|
+
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
protected CallMacro(outer: UnitCall, macro: MacroFunction): (expr: UnitCall) => UnionValue /*UnionOrArray*/ {
|
|
437
|
+
|
|
438
|
+
if (!macro.expression) {
|
|
439
|
+
return () => ExpressionError();
|
|
440
|
+
}
|
|
441
|
+
const text_expr = JSON.stringify(macro.expression);
|
|
442
|
+
const names: Record<string, ExpressionUnit> = {};
|
|
443
|
+
const upper_case_names = macro.argument_names?.map(name => name.toUpperCase()) || [];
|
|
444
|
+
|
|
445
|
+
return (expr: UnitCall) => {
|
|
446
|
+
|
|
447
|
+
const clone = JSON.parse(text_expr);
|
|
448
|
+
|
|
449
|
+
for (let i = 0; i < upper_case_names.length; i++) {
|
|
450
|
+
names[upper_case_names[i]] = expr.args[i] || { type: 'missing', id: 0 };
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return this.CalculateExpression(this.RewriteMacro(clone, names) as ExtendedExpressionUnit);
|
|
454
|
+
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* excutes a function call
|
|
461
|
+
*
|
|
462
|
+
* the return type of functions has never been locked down, and as a result
|
|
463
|
+
* there are a couple of things we need to handle.
|
|
464
|
+
*
|
|
465
|
+
* return type can be any value, essentially, or array, error object, or
|
|
466
|
+
* (in the case of some of the reference/lookup functions) an address or
|
|
467
|
+
* range expression. array must be 2d, I think? not sure that that is true.
|
|
468
|
+
*
|
|
469
|
+
* this wrapper function returns a function which returns one of those
|
|
470
|
+
* things, i.e. it returns (expr) => return type
|
|
471
|
+
*
|
|
472
|
+
* it will only return address/range if the parameter flag is set, so we
|
|
473
|
+
* could in theory lock it down a bit with overloads.
|
|
474
|
+
*
|
|
475
|
+
* ---
|
|
476
|
+
*
|
|
477
|
+
* UPDATE: that's no longer the case. we require that functions return
|
|
478
|
+
* a UnionValue type (union), which can itself contain an array.
|
|
479
|
+
*
|
|
480
|
+
* ---
|
|
481
|
+
*
|
|
482
|
+
* FIXME: there is far too much duplication between this and the MC version
|
|
483
|
+
* (in simulation-expression-calculator). we need to find a way to consolidate
|
|
484
|
+
* these.
|
|
485
|
+
*
|
|
486
|
+
* I think the problem is that we don't want a lot of switches, but the cost
|
|
487
|
+
* is an almost complete duplicate of this function in the subclass.
|
|
488
|
+
*
|
|
489
|
+
*/
|
|
490
|
+
protected CallExpression(outer: UnitCall, return_reference = false): (expr: UnitCall) => UnionValue /*UnionOrArray*/ {
|
|
491
|
+
|
|
492
|
+
// get the function descriptor, which won't change.
|
|
493
|
+
// we can bind in closure (also short-circuit check for
|
|
494
|
+
// invalid name)
|
|
495
|
+
|
|
496
|
+
const func = this.library.Get(outer.name);
|
|
497
|
+
|
|
498
|
+
if (!func) {
|
|
499
|
+
return () => NameError();
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return (expr: UnitCall) => {
|
|
503
|
+
|
|
504
|
+
// get an index we can use for this call (we may recurse when
|
|
505
|
+
// calculating arguments), then increment for the next call.
|
|
506
|
+
|
|
507
|
+
const call_index = this.call_index++;
|
|
508
|
+
|
|
509
|
+
// yeah so this is clear. just checking volatile.
|
|
510
|
+
|
|
511
|
+
// FIXME: should this be set later, at the same time as the
|
|
512
|
+
// calculation index? I think it should, since we may recurse.
|
|
513
|
+
|
|
514
|
+
// BEFORE YOU DO THAT, track down all references that read this field
|
|
515
|
+
|
|
516
|
+
// from what I can tell, the only place this is read is after the
|
|
517
|
+
// external (outer) Calculate() call. so we should move this assignment,
|
|
518
|
+
// and we should also be able to get it to fail:
|
|
519
|
+
//
|
|
520
|
+
// RandBetween() should be volatile, but if we have a nonvolatile function
|
|
521
|
+
// as an argument that should unset it, and remove the volatile flag.
|
|
522
|
+
// Check?
|
|
523
|
+
|
|
524
|
+
// actually this works, because it only sets the flag (does not unset).
|
|
525
|
+
// volatile applies to the _cell_, not just the function -- so as long
|
|
526
|
+
// as the outer function sets the flag, it's not material if an inner
|
|
527
|
+
// function is nonvolatile. similarly an inner volatile function will
|
|
528
|
+
// make the outer function volatile.
|
|
529
|
+
|
|
530
|
+
// this does mean that the nonvolatile function will be treated differently
|
|
531
|
+
// if it's an argument to a volatile function, but I think that's reasonable
|
|
532
|
+
// behavior; also it's symmetric with the opposite case (inner volatile.)
|
|
533
|
+
|
|
534
|
+
// so leave this as-is, or you can move it -- should be immaterial
|
|
535
|
+
|
|
536
|
+
this.context.volatile = this.context.volatile || (!!func.volatile);
|
|
537
|
+
|
|
538
|
+
// NOTE: the argument logic is (possibly) calculating unecessary operations,
|
|
539
|
+
// if there's a conditional (like an IF function). although that is the
|
|
540
|
+
// exception rather than the rule...
|
|
541
|
+
|
|
542
|
+
// ok we can handle IF functions, at the expense of some tests...
|
|
543
|
+
// is it worth it?
|
|
544
|
+
|
|
545
|
+
const if_function = outer.name.toLowerCase() === 'if';
|
|
546
|
+
let skip_argument_index = -1;
|
|
547
|
+
|
|
548
|
+
let argument_error: UnionValue|undefined;
|
|
549
|
+
|
|
550
|
+
const argument_descriptors = func.arguments || []; // map
|
|
551
|
+
|
|
552
|
+
const mapped_args = expr.args.map((arg, arg_index) => {
|
|
553
|
+
|
|
554
|
+
// short circuit
|
|
555
|
+
if (argument_error) {
|
|
556
|
+
return undefined;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// get descriptor. if the number of arguments exceeds
|
|
560
|
+
// the number of descriptors, recycle the last one
|
|
561
|
+
const descriptor = argument_descriptors[Math.min(arg_index, argument_descriptors.length - 1)] || {};
|
|
562
|
+
|
|
563
|
+
// if function, wrong branch
|
|
564
|
+
if (arg_index === skip_argument_index) {
|
|
565
|
+
return descriptor.boxed ? { type: ValueType.undefined } : undefined;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// note on type here: we're iterating over the arguments
|
|
569
|
+
// described by the parse expression, not the values. although
|
|
570
|
+
// in this case, wouldn't this be a missing type? (...)
|
|
571
|
+
if (typeof arg === 'undefined') {
|
|
572
|
+
if (if_function && arg_index === 0) { skip_argument_index = 1; }
|
|
573
|
+
return descriptor.boxed ? { type: ValueType.undefined } : undefined;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// FIXME (address): what about named ranges (actually those will work),
|
|
577
|
+
// constructed references (we don't support them atm)?
|
|
578
|
+
|
|
579
|
+
// NOTE: named ranges will _not_ work, because the address will be an
|
|
580
|
+
// object, not a string. so FIXME.
|
|
581
|
+
|
|
582
|
+
if (descriptor.address) {
|
|
583
|
+
return descriptor.boxed ? {
|
|
584
|
+
type: ValueType.string,
|
|
585
|
+
value: this.parser.Render(arg).replace(/\$/g, ''),
|
|
586
|
+
} : this.parser.Render(arg).replace(/\$/g, '');
|
|
587
|
+
}
|
|
588
|
+
else if (descriptor.metadata) {
|
|
589
|
+
|
|
590
|
+
return this.GetMetadata(arg, () => { return {}}); // type is UnionOrArray
|
|
591
|
+
|
|
592
|
+
}
|
|
593
|
+
else {
|
|
594
|
+
|
|
595
|
+
const result = this.CalculateExpression(arg as ExtendedExpressionUnit);
|
|
596
|
+
|
|
597
|
+
// if (!Array.isArray(result) && result.type === ValueType.error) {
|
|
598
|
+
|
|
599
|
+
if (result.type === ValueType.error) { // array check is implicit since array is a type
|
|
600
|
+
if (descriptor.allow_error) {
|
|
601
|
+
return result; // always boxed
|
|
602
|
+
}
|
|
603
|
+
argument_error = result;
|
|
604
|
+
return undefined; // argument not used, so don't bother boxing
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// can't shortcut if you have an array (or we need to test all the values)
|
|
608
|
+
|
|
609
|
+
//if (if_function && arg_index === 0 && !Array.isArray(result)) {
|
|
610
|
+
if (if_function && arg_index === 0 && result.type !== ValueType.array){ // !Array.isArray(result)) {
|
|
611
|
+
let result_truthy = false;
|
|
612
|
+
|
|
613
|
+
// if (Array.isArray(result)) { result_truthy = true; }
|
|
614
|
+
|
|
615
|
+
if (result.type === ValueType.string) {
|
|
616
|
+
const lowercase = (result.value as string).toLowerCase().trim();
|
|
617
|
+
result_truthy = lowercase !== 'false' && lowercase !== 'f';
|
|
618
|
+
}
|
|
619
|
+
else {
|
|
620
|
+
result_truthy = !!result.value;
|
|
621
|
+
}
|
|
622
|
+
skip_argument_index = result_truthy ? 2 : 1;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
if (descriptor.boxed) {
|
|
626
|
+
return result;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/*
|
|
630
|
+
if (Array.isArray(result)) {
|
|
631
|
+
return result.map(row => row.map(value => value.value));
|
|
632
|
+
}
|
|
633
|
+
*/
|
|
634
|
+
if (result.type === ValueType.array) {
|
|
635
|
+
return (result as ArrayUnion).value.map(row => row.map(value => value.value));
|
|
636
|
+
}
|
|
637
|
+
else {
|
|
638
|
+
return result.value; // unboxing
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
if (argument_error) {
|
|
646
|
+
return argument_error;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// if we have any nested calls, they may have updated the index so
|
|
650
|
+
// we use the captured value here.
|
|
651
|
+
|
|
652
|
+
this.context.call_index = call_index;
|
|
653
|
+
|
|
654
|
+
// I thought we were passing the model as this (...) ? actually
|
|
655
|
+
// now we bind functions that need this, so maybe we should pass
|
|
656
|
+
// null here.
|
|
657
|
+
|
|
658
|
+
// return func.fn.apply(null, mapped_args);
|
|
659
|
+
|
|
660
|
+
if (func.return_type === ReturnType.reference) {
|
|
661
|
+
|
|
662
|
+
const result = func.fn.apply(null, mapped_args);
|
|
663
|
+
|
|
664
|
+
if (return_reference) {
|
|
665
|
+
return result;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
if (UnionIsExpressionUnit(result)) {
|
|
669
|
+
if (result.value.type === 'address') {
|
|
670
|
+
return this.CellFunction2(result.value)();
|
|
671
|
+
}
|
|
672
|
+
else if (result.value.type === 'range') {
|
|
673
|
+
return this.CellFunction4(result.value.start, result.value.end)
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
return result; // error?
|
|
678
|
+
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
return func.fn.apply(null, mapped_args);
|
|
682
|
+
|
|
683
|
+
};
|
|
684
|
+
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
protected ResolveStructuredReference(expr: UnitStructuredReference): () => UnionValue {
|
|
688
|
+
|
|
689
|
+
// basically our approach here is to resolve the structured reference
|
|
690
|
+
// to a concrete reference.
|
|
691
|
+
//
|
|
692
|
+
// if the structured reference changes, then it will get recalculated
|
|
693
|
+
// (and hence rebuilt). if the table name or a referenced column name
|
|
694
|
+
// changes, the cell will get rewritten so again, it will get recalculated.
|
|
695
|
+
//
|
|
696
|
+
// the case we have to worry about is if the table layout changes: if a
|
|
697
|
+
// column is added or removed. because in that case, our reference will
|
|
698
|
+
// be out of date but we won't be notified about it.
|
|
699
|
+
//
|
|
700
|
+
// so we will have to make sure that if a table layout changes, columns
|
|
701
|
+
// or rows added or deleted, then we invalidate the entire table. if we
|
|
702
|
+
// do that this should all work out.
|
|
703
|
+
|
|
704
|
+
const resolved = this.data_model.ResolveStructuredReference(expr, this.context.address);
|
|
705
|
+
if (resolved) {
|
|
706
|
+
if (resolved.type === 'address') {
|
|
707
|
+
return this.CellFunction2(resolved);
|
|
708
|
+
}
|
|
709
|
+
else if(resolved.type === 'range') {
|
|
710
|
+
return () => this.CellFunction4(resolved.start, resolved.end);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
return () => ReferenceError();
|
|
715
|
+
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
protected ResolveDimensionedQuantity(): (exp: UnitDimensionedQuantity) => UnionValue {
|
|
719
|
+
|
|
720
|
+
return (expr: UnitDimensionedQuantity): UnionValue => {
|
|
721
|
+
const expression = this.CalculateExpression(expr.expression as ExtendedExpressionUnit);
|
|
722
|
+
return {
|
|
723
|
+
type: ValueType.dimensioned_quantity,
|
|
724
|
+
value: {
|
|
725
|
+
value: expression.value,
|
|
726
|
+
unit: expr.unit.name,
|
|
727
|
+
},
|
|
728
|
+
};
|
|
729
|
+
};
|
|
730
|
+
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
protected UnaryExpression(x: UnitUnary): (expr: UnitUnary) => UnionValue /*UnionOrArray*/ { // operator: string, operand: any){
|
|
734
|
+
|
|
735
|
+
// there are basically three code paths here: negate, identity, and error.
|
|
736
|
+
// they have very different semantics so we're going to do them completely
|
|
737
|
+
// separately.
|
|
738
|
+
|
|
739
|
+
switch (x.operator) {
|
|
740
|
+
case '+':
|
|
741
|
+
return (expr: UnitUnary) => {
|
|
742
|
+
return this.CalculateExpression(expr.operand as ExtendedExpressionUnit);
|
|
743
|
+
};
|
|
744
|
+
|
|
745
|
+
case '-':
|
|
746
|
+
{
|
|
747
|
+
const func = Primitives.Subtract;
|
|
748
|
+
const zero = { type: ValueType.number, value: 0 } as NumberUnion;
|
|
749
|
+
|
|
750
|
+
return (expr: UnitUnary) => {
|
|
751
|
+
const operand = this.CalculateExpression(expr.operand as ExtendedExpressionUnit);
|
|
752
|
+
if (operand.type === ValueType.array) {
|
|
753
|
+
return {
|
|
754
|
+
type: ValueType.array,
|
|
755
|
+
value: (operand as ArrayUnion).value.map(column => column.map(value => func(zero, value))),
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
/*
|
|
759
|
+
if (Array.isArray(operand)) {
|
|
760
|
+
return operand.map(column => column.map(value => func(zero, value)));
|
|
761
|
+
}
|
|
762
|
+
*/
|
|
763
|
+
return func(zero, operand);
|
|
764
|
+
};
|
|
765
|
+
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
default:
|
|
769
|
+
return () => {
|
|
770
|
+
console.warn('unexpected unary operator:', x.operator);
|
|
771
|
+
return ExpressionError();
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/**
|
|
778
|
+
* expands the size of an array by recycling values in columns and rows
|
|
779
|
+
*
|
|
780
|
+
* FIXME: seems like this is more a generic thing, -> utils lib
|
|
781
|
+
*
|
|
782
|
+
* @param arr 2d array
|
|
783
|
+
* @param columns target columns
|
|
784
|
+
* @param rows target rows
|
|
785
|
+
*/
|
|
786
|
+
protected RecycleArray<T>(arr: T[][], columns: number, rows: number): T[][] {
|
|
787
|
+
|
|
788
|
+
// NOTE: recycle rows first, more efficient. do it in place?
|
|
789
|
+
|
|
790
|
+
if (arr[0].length < rows) {
|
|
791
|
+
const len = arr[0].length;
|
|
792
|
+
for (const column of arr) {
|
|
793
|
+
for (let r = len; r < rows; r++ ) {
|
|
794
|
+
column[r] = column[r % len];
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
if (arr.length < columns) {
|
|
800
|
+
const len = arr.length;
|
|
801
|
+
for (let c = len; c < columns; c++) arr[c] = arr[c % len].slice(0);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
return arr;
|
|
805
|
+
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
//protected ElementwiseBinaryExpression(fn: Primitives.PrimitiveBinaryExpression, left: UnionValue[][], right: UnionValue[][]): UnionValue[][] {
|
|
809
|
+
protected ElementwiseBinaryExpression(fn: Primitives.PrimitiveBinaryExpression, left: ArrayUnion, right: ArrayUnion): ArrayUnion {
|
|
810
|
+
|
|
811
|
+
const columns = Math.max(left.value.length, right.value.length);
|
|
812
|
+
const rows = Math.max(left.value[0].length, right.value[0].length);
|
|
813
|
+
|
|
814
|
+
// const columns = Math.max(left.length, right.length);
|
|
815
|
+
// const rows = Math.max(left[0].length, right[0].length);
|
|
816
|
+
|
|
817
|
+
const left_values = this.RecycleArray(left.value, columns, rows);
|
|
818
|
+
const right_values = this.RecycleArray(right.value, columns, rows);
|
|
819
|
+
|
|
820
|
+
const value: UnionValue[][] = [];
|
|
821
|
+
|
|
822
|
+
for (let c = 0; c < columns; c++) {
|
|
823
|
+
const col = [];
|
|
824
|
+
for (let r = 0; r < rows; r++ ) {
|
|
825
|
+
col[r] = fn(left_values[c][r], right_values[c][r]);
|
|
826
|
+
}
|
|
827
|
+
value.push(col);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
return { type: ValueType.array, value };
|
|
831
|
+
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
protected BinaryExpression(x: UnitBinary): (expr: UnitBinary) => UnionValue /*UnionOrArray*/ {
|
|
835
|
+
|
|
836
|
+
// we are constructing and caching functions for binary expressions.
|
|
837
|
+
// this should simplify calls when parameters change. eventually I'd
|
|
838
|
+
// like to do this for other dynamic calls as well...
|
|
839
|
+
|
|
840
|
+
// the idea is that we can start composing compound expressions. still
|
|
841
|
+
// not sure if that will work (or if it's a good idea).
|
|
842
|
+
|
|
843
|
+
// NOTE (for the future?) if one or both of the operands is a literal,
|
|
844
|
+
// we can bind that directly. literals in the expression won't change
|
|
845
|
+
// unless the expression changes, which will discard the generated
|
|
846
|
+
// function (along with the expression itself).
|
|
847
|
+
|
|
848
|
+
const fn = Primitives.MapOperator(x.operator);
|
|
849
|
+
|
|
850
|
+
if (!fn) {
|
|
851
|
+
return () => { // expr: UnitBinary) => {
|
|
852
|
+
console.info(`(unexpected binary operator: ${x.operator})`);
|
|
853
|
+
return ExpressionError();
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
else {
|
|
857
|
+
return (expr: UnitBinary) => {
|
|
858
|
+
|
|
859
|
+
// sloppy typing, to support operators? (...)
|
|
860
|
+
|
|
861
|
+
const left = this.CalculateExpression(expr.left as ExtendedExpressionUnit);
|
|
862
|
+
const right = this.CalculateExpression(expr.right as ExtendedExpressionUnit);
|
|
863
|
+
|
|
864
|
+
// check for arrays. do elementwise operations.
|
|
865
|
+
|
|
866
|
+
if (left.type === ValueType.array) {
|
|
867
|
+
if (right.type === ValueType.array) {
|
|
868
|
+
return this.ElementwiseBinaryExpression(fn, left as ArrayUnion, right as ArrayUnion);
|
|
869
|
+
}
|
|
870
|
+
return this.ElementwiseBinaryExpression(fn, left as ArrayUnion, {type: ValueType.array, value: [[right]]});
|
|
871
|
+
}
|
|
872
|
+
else if (right.type === ValueType.array) {
|
|
873
|
+
return this.ElementwiseBinaryExpression(fn, {type: ValueType.array, value: [[left]]}, right as ArrayUnion);
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
return fn(left, right);
|
|
877
|
+
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
protected Identifier(expr: UnitIdentifier): () => UnionValue /*UnionOrArray*/ {
|
|
884
|
+
|
|
885
|
+
// NOTE: TRUE and FALSE don't get here -- they are converted
|
|
886
|
+
// to literals by the parser? (...)
|
|
887
|
+
|
|
888
|
+
// the function we create here binds the name because
|
|
889
|
+
// this is a literal identifier. if the value were to change,
|
|
890
|
+
// the expression would be discarded.
|
|
891
|
+
|
|
892
|
+
// however we have to do the lookup dynamically because the
|
|
893
|
+
// underlying reference (in the named range map) might change.
|
|
894
|
+
|
|
895
|
+
// although it's worth noting that, atm at least, that wouldn't
|
|
896
|
+
// trigger an update because it's not considered a value change.
|
|
897
|
+
// you'd have to recalc, which would rebuild the expression anyway.
|
|
898
|
+
// call that a FIXME? (...)
|
|
899
|
+
|
|
900
|
+
const identifier = expr.name;
|
|
901
|
+
|
|
902
|
+
// anything starting with # is an error. the only thing we should
|
|
903
|
+
// have is #REF, but maybe that will change in the future.
|
|
904
|
+
|
|
905
|
+
if (identifier[0] === '#') {
|
|
906
|
+
return () => ReferenceError();
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
const upper_case = identifier.toUpperCase();
|
|
910
|
+
|
|
911
|
+
switch (upper_case){
|
|
912
|
+
case 'FALSE':
|
|
913
|
+
case 'F':
|
|
914
|
+
return () => {return {value: false, type: ValueType.boolean}};
|
|
915
|
+
|
|
916
|
+
case 'TRUE':
|
|
917
|
+
case 'T':
|
|
918
|
+
return () => {return {value: true, type: ValueType.boolean}};
|
|
919
|
+
|
|
920
|
+
case 'UNDEFINED':
|
|
921
|
+
return () => {return {value: undefined, type: ValueType.undefined}}; // why do we support this?
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
return () => {
|
|
925
|
+
|
|
926
|
+
/*
|
|
927
|
+
if (this.bound_name_stack[0]) {
|
|
928
|
+
const expr = this.bound_name_stack[0][upper_case];
|
|
929
|
+
if (expr) {
|
|
930
|
+
console.info("BOUND", upper_case, expr);
|
|
931
|
+
return this.CalculateExpression(expr as ExtendedExpressionUnit);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
*/
|
|
935
|
+
|
|
936
|
+
const named_range = this.named_range_map[upper_case];
|
|
937
|
+
|
|
938
|
+
if (named_range) {
|
|
939
|
+
if (named_range.count === 1) {
|
|
940
|
+
return this.CellFunction4(named_range.start, named_range.start);
|
|
941
|
+
}
|
|
942
|
+
else {
|
|
943
|
+
return this.CellFunction4(named_range.start, named_range.end);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
const named_expression = this.data_model.named_expressions.get(upper_case);
|
|
948
|
+
if (named_expression) {
|
|
949
|
+
return this.CalculateExpression(named_expression as ExtendedExpressionUnit);
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
/*
|
|
953
|
+
const bound_names = this.context.name_stack[0];
|
|
954
|
+
|
|
955
|
+
if (bound_names && bound_names[upper_case]) {
|
|
956
|
+
const bound_expression = bound_names[upper_case];
|
|
957
|
+
return this.CalculateExpression(bound_expression);
|
|
958
|
+
}
|
|
959
|
+
*/
|
|
960
|
+
|
|
961
|
+
// console.info( '** identifier', {identifier, expr, context: this.context});
|
|
962
|
+
|
|
963
|
+
return NameError();
|
|
964
|
+
|
|
965
|
+
};
|
|
966
|
+
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
protected GroupExpression(x: UnitGroup): (expr: UnitGroup) => UnionValue /*UnionOrArray*/ {
|
|
970
|
+
|
|
971
|
+
// a group is an expression in parentheses, either explicit
|
|
972
|
+
// (from the user) or implicit (created to manage operation
|
|
973
|
+
// priority, order of operations, or similar).
|
|
974
|
+
|
|
975
|
+
// expressions nest, so there's no case where a group should
|
|
976
|
+
// have length !== 1 -- consider that an error.
|
|
977
|
+
|
|
978
|
+
if (!x.elements || x.elements.length !== 1){
|
|
979
|
+
console.warn( `Can't handle group !== 1` );
|
|
980
|
+
return () => ExpressionError();
|
|
981
|
+
}
|
|
982
|
+
return (expr: UnitGroup) => this.CalculateExpression(expr.elements[0] as ExtendedExpressionUnit);
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
protected CalculateExpression(expr: ExtendedExpressionUnit, return_reference = false): UnionValue /*UnionOrArray*/ {
|
|
986
|
+
|
|
987
|
+
// user data is a generated function for the expression, at least
|
|
988
|
+
// for the simple ones (atm). see BinaryExpression for more. the
|
|
989
|
+
// aim is to remove as many tests and lookups as possible.
|
|
990
|
+
|
|
991
|
+
// may be over-optimizing here.
|
|
992
|
+
|
|
993
|
+
if (expr.user_data) {
|
|
994
|
+
return expr.user_data(expr);
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
switch (expr.type){
|
|
998
|
+
case 'call':
|
|
999
|
+
{
|
|
1000
|
+
const macro = this.data_model.macro_functions.get(expr.name.toUpperCase());
|
|
1001
|
+
if (macro) {
|
|
1002
|
+
return (expr.user_data = this.CallMacro(expr, macro))(expr);
|
|
1003
|
+
}
|
|
1004
|
+
return (expr.user_data = this.CallExpression(expr, return_reference))(expr);
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
case 'address':
|
|
1008
|
+
return (expr.user_data = this.CellFunction2(expr))(); // check
|
|
1009
|
+
|
|
1010
|
+
case 'range':
|
|
1011
|
+
return (expr.user_data = (x: UnitRange) => this.CellFunction4(x.start, x.end))(expr); // check
|
|
1012
|
+
|
|
1013
|
+
case 'binary':
|
|
1014
|
+
return (expr.user_data = this.BinaryExpression(expr))(expr); // check
|
|
1015
|
+
|
|
1016
|
+
case 'unary':
|
|
1017
|
+
return (expr.user_data = this.UnaryExpression(expr))(expr); // check
|
|
1018
|
+
|
|
1019
|
+
case 'identifier':
|
|
1020
|
+
return (expr.user_data = this.Identifier(expr))(); // check
|
|
1021
|
+
|
|
1022
|
+
case 'missing':
|
|
1023
|
+
return (expr.user_data = () => { return { value: undefined, type: ValueType.undefined } as UndefinedUnion })(); // check
|
|
1024
|
+
|
|
1025
|
+
case 'dimensioned':
|
|
1026
|
+
return (expr.user_data = this.ResolveDimensionedQuantity())(expr);
|
|
1027
|
+
|
|
1028
|
+
case 'literal':
|
|
1029
|
+
{
|
|
1030
|
+
const literal = { value: expr.value, type: GetValueType(expr.value) } as UnionValue;
|
|
1031
|
+
return (expr.user_data = () => literal)(); // check
|
|
1032
|
+
}
|
|
1033
|
+
case 'group':
|
|
1034
|
+
return (expr.user_data = this.GroupExpression(expr))(expr); // check
|
|
1035
|
+
|
|
1036
|
+
case 'complex':
|
|
1037
|
+
{
|
|
1038
|
+
const literal = {value: {real: expr.real, imaginary: expr.imaginary}, type: ValueType.complex } as ComplexUnion;
|
|
1039
|
+
return (expr.user_data = () => literal)(); // check
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
case 'structured-reference':
|
|
1043
|
+
return (expr.user_data = this.ResolveStructuredReference(expr))();
|
|
1044
|
+
|
|
1045
|
+
case 'array':
|
|
1046
|
+
{
|
|
1047
|
+
return (expr.user_data = () => {
|
|
1048
|
+
return {
|
|
1049
|
+
type: ValueType.array,
|
|
1050
|
+
value: expr.values.map((row: any) => (Array.isArray(row) ? row : [row]).map((value: any) => {
|
|
1051
|
+
return { type: GetValueType(value), value } as UnionValue;
|
|
1052
|
+
})),
|
|
1053
|
+
} as ArrayUnion;
|
|
1054
|
+
})();
|
|
1055
|
+
/*
|
|
1056
|
+
return (expr.user_data = () => expr.values.map(row => (Array.isArray(row) ? row : [row]).map(value => {
|
|
1057
|
+
return { value, type: GetValueType(value) }
|
|
1058
|
+
})))(); // check
|
|
1059
|
+
*/
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
default:
|
|
1063
|
+
console.warn( 'Unhandled parse expr:', expr);
|
|
1064
|
+
return UnknownError();
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
}
|
|
1069
|
+
|