@trebco/treb 23.6.5 → 25.0.0-rc1
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} +285 -269
- package/esbuild-custom-element.mjs +336 -0
- package/esbuild.js +305 -0
- package/package.json +43 -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 +1227 -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 +5362 -0
- package/treb-embed/src/index.ts +16 -0
- package/treb-embed/src/language-model.ts +41 -0
- package/treb-embed/src/options.ts +320 -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,1214 @@
|
|
|
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 { FunctionMap } from '../descriptors';
|
|
23
|
+
import * as Utils from '../utilities';
|
|
24
|
+
import { ReferenceError, NotImplError, NAError, ArgumentError, DivideByZeroError, ValueError } from '../function-error';
|
|
25
|
+
import { Box, UnionValue, ValueType, GetValueType,
|
|
26
|
+
RenderFunctionResult, RenderFunctionOptions, ComplexOrReal, Complex } from 'treb-base-types';
|
|
27
|
+
import { Sparkline } from './sparkline';
|
|
28
|
+
import { LotusDate, UnlotusDate } from 'treb-format';
|
|
29
|
+
|
|
30
|
+
import { ClickCheckbox, RenderCheckbox } from './checkbox';
|
|
31
|
+
import { UnionIsMetadata } from '../expression-calculator';
|
|
32
|
+
|
|
33
|
+
import { Exp as ComplexExp, Power as ComplexPower, Multiply as ComplexMultply } from '../complex-math';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* BaseFunctionLibrary is a static object that has basic spreadsheet
|
|
37
|
+
* functions and associated metadata (there's also a list of aliases).
|
|
38
|
+
*
|
|
39
|
+
* Calculator should register this one first, followed by any other
|
|
40
|
+
* application-specific libraries.
|
|
41
|
+
*
|
|
42
|
+
* FIXME: there's no reason this has to be a single, monolithic library.
|
|
43
|
+
* we could split up by category or something.
|
|
44
|
+
*
|
|
45
|
+
* ALSO: add category to descriptor.
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
/** milliseconds in one day, used in time functions */
|
|
49
|
+
// const DAY_MS = 1000 * 60 * 60 * 24;
|
|
50
|
+
|
|
51
|
+
// some functions have semantics that can't be represented inline,
|
|
52
|
+
// or we may want to refer to them from other functions.
|
|
53
|
+
|
|
54
|
+
// OK, just one.
|
|
55
|
+
|
|
56
|
+
/** error function (for gaussian distribution) */
|
|
57
|
+
const erf = (x: number): number => {
|
|
58
|
+
|
|
59
|
+
const a1 = 0.254829592;
|
|
60
|
+
const a2 = -0.284496736;
|
|
61
|
+
const a3 = 1.421413741;
|
|
62
|
+
const a4 = -1.453152027;
|
|
63
|
+
const a5 = 1.061405429;
|
|
64
|
+
const p = 0.3275911;
|
|
65
|
+
|
|
66
|
+
x = Math.abs(x);
|
|
67
|
+
const t = 1 / (1 + p * x);
|
|
68
|
+
return 1 - ((((((a5 * t + a4) * t) + a3) * t + a2) * t) + a1) * t * Math.exp(-1 * x * x);
|
|
69
|
+
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const sqrt2pi = Math.sqrt(2 * Math.PI);
|
|
73
|
+
|
|
74
|
+
/** imprecise but reasonably fast normsinv function */
|
|
75
|
+
const inverse_normal = (q: number): number => {
|
|
76
|
+
|
|
77
|
+
if (q === 0.50) {
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const p = (q < 1.0 && q > 0.5) ? (1 - q) : q;
|
|
82
|
+
const t = Math.sqrt(Math.log(1.0 / Math.pow(p, 2.0)));
|
|
83
|
+
const x = t - (2.515517 + 0.802853 * t + 0.010328 * Math.pow(t, 2.0)) /
|
|
84
|
+
(1.0 + 1.432788 * t + 0.189269 * Math.pow(t, 2.0) + 0.001308 * Math.pow(t, 3.0));
|
|
85
|
+
|
|
86
|
+
return (q > 0.5 ? x : -x);
|
|
87
|
+
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* alternate functions. these are used (atm) only for changing complex
|
|
92
|
+
* behavior.
|
|
93
|
+
*/
|
|
94
|
+
export const AltFunctionLibrary: FunctionMap = {
|
|
95
|
+
|
|
96
|
+
Sqrt: {
|
|
97
|
+
description: 'Returns the square root of the argument',
|
|
98
|
+
arguments: [
|
|
99
|
+
{boxed: true},
|
|
100
|
+
],
|
|
101
|
+
fn: Utils.ApplyAsArray((ref: UnionValue): UnionValue => {
|
|
102
|
+
|
|
103
|
+
if (ref.type === ValueType.complex) {
|
|
104
|
+
const value = ComplexPower(ref.value, {real: 0.5, imaginary: 0});
|
|
105
|
+
return ComplexOrReal(value);
|
|
106
|
+
}
|
|
107
|
+
else if (ref.type === ValueType.undefined || !ref.value) {
|
|
108
|
+
return {
|
|
109
|
+
type: ValueType.number, value: 0,
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
else if (ref.type === ValueType.number && ref.value < 0) {
|
|
113
|
+
const value = ComplexPower({real: ref.value, imaginary: 0}, {real: 0.5, imaginary: 0});
|
|
114
|
+
return {
|
|
115
|
+
type: ValueType.complex,
|
|
116
|
+
value,
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
const value = Math.sqrt(ref.value);
|
|
121
|
+
if (isNaN(value)) {
|
|
122
|
+
return ValueError();
|
|
123
|
+
}
|
|
124
|
+
return { type: ValueType.number, value };
|
|
125
|
+
}
|
|
126
|
+
}),
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
Power: {
|
|
130
|
+
description: 'Returns base raised to the given power',
|
|
131
|
+
arguments: [
|
|
132
|
+
{ name: 'base', boxed: true, },
|
|
133
|
+
{ name: 'exponent', boxed: true, }
|
|
134
|
+
],
|
|
135
|
+
fn: Utils.ApplyAsArray2((base: UnionValue, exponent: UnionValue): UnionValue => {
|
|
136
|
+
|
|
137
|
+
// we're leaking complex numbers here because our functions are
|
|
138
|
+
// very slightly imprecise. I would like to stop doing that. try to
|
|
139
|
+
// use real math unless absolutely necessary.
|
|
140
|
+
|
|
141
|
+
// in the alternative we could update the epsilon on our ComplexOrReal
|
|
142
|
+
// function, but I would prefer not to do that if we don't have to.
|
|
143
|
+
|
|
144
|
+
// so: if both arguments are real, and base is >= 0 we can use real math.
|
|
145
|
+
// also if exponent is either 0 or >= 1 we can use real math.
|
|
146
|
+
|
|
147
|
+
if (base.type === ValueType.number && exponent.type === ValueType.number) {
|
|
148
|
+
if (base.value >= 0 || exponent.value === 0 || Math.abs(exponent.value) >= 1) {
|
|
149
|
+
const value = Math.pow(base.value, exponent.value);
|
|
150
|
+
if (isNaN(value)) {
|
|
151
|
+
return ValueError();
|
|
152
|
+
}
|
|
153
|
+
return { type: ValueType.number, value };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/*
|
|
158
|
+
if (base.type === ValueType.number) {
|
|
159
|
+
base = {
|
|
160
|
+
type: ValueType.complex,
|
|
161
|
+
value: { imaginary: 0, real: base.value },
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
*/
|
|
165
|
+
|
|
166
|
+
const a = base.type === ValueType.complex ? base.value :
|
|
167
|
+
{ real: base.value || 0, imaginary: 0, };
|
|
168
|
+
|
|
169
|
+
const b = exponent.type === ValueType.complex ? exponent.value :
|
|
170
|
+
{ real: exponent.value || 0, imaginary: 0, };
|
|
171
|
+
|
|
172
|
+
const value = ComplexPower(a, b);
|
|
173
|
+
return ComplexOrReal(value);
|
|
174
|
+
|
|
175
|
+
}),
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// use a single, static object for base functions
|
|
181
|
+
|
|
182
|
+
export const BaseFunctionLibrary: FunctionMap = {
|
|
183
|
+
|
|
184
|
+
Int: {
|
|
185
|
+
fn: (value: number) => {
|
|
186
|
+
return {type: ValueType.number, value: Math.floor(value) };
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
Rand: {
|
|
191
|
+
volatile: true,
|
|
192
|
+
fn: () => { return { type: ValueType.number, value: Math.random() }},
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
RandBetween: {
|
|
196
|
+
arguments: [{name: 'min'}, {name: 'max'}],
|
|
197
|
+
volatile: true,
|
|
198
|
+
fn: (min = 0, max = 1) => {
|
|
199
|
+
if (min > max) {
|
|
200
|
+
const tmp = min;
|
|
201
|
+
min = max;
|
|
202
|
+
max = tmp;
|
|
203
|
+
}
|
|
204
|
+
return { type: ValueType.number, value: Math.floor(Math.random() * (max + 1 - min) + min) }
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
Sum: {
|
|
209
|
+
description: 'Adds arguments and ranges',
|
|
210
|
+
arguments: [{ boxed: true, name: 'values or ranges' }],
|
|
211
|
+
fn: (...args: UnionValue[]) => {
|
|
212
|
+
|
|
213
|
+
const sum = { real: 0, imaginary: 0 };
|
|
214
|
+
|
|
215
|
+
const values = Utils.FlattenBoxed(args); // as UnionValue[];
|
|
216
|
+
|
|
217
|
+
for (const value of values) {
|
|
218
|
+
|
|
219
|
+
switch (value.type) {
|
|
220
|
+
case ValueType.number: sum.real += value.value; break;
|
|
221
|
+
case ValueType.boolean: sum.real += (value.value ? 1 : 0); break;
|
|
222
|
+
case ValueType.complex:
|
|
223
|
+
sum.real += value.value.real;
|
|
224
|
+
sum.imaginary += value.value.imaginary;
|
|
225
|
+
break;
|
|
226
|
+
case ValueType.error: return value;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return ComplexOrReal(sum);
|
|
231
|
+
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
|
|
235
|
+
Now: {
|
|
236
|
+
description: 'Returns current time',
|
|
237
|
+
volatile: true,
|
|
238
|
+
fn: () => {
|
|
239
|
+
return { type: ValueType.number, value: UnlotusDate(new Date().getTime()) };
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
|
|
243
|
+
Date: {
|
|
244
|
+
description: 'Constructs a Lotus date from parts',
|
|
245
|
+
arguments: [
|
|
246
|
+
{ name: 'year' },
|
|
247
|
+
{ name: 'month' },
|
|
248
|
+
{ name: 'day' },
|
|
249
|
+
],
|
|
250
|
+
fn: (year: number, month: number, day: number) => {
|
|
251
|
+
const date = new Date();
|
|
252
|
+
date.setMilliseconds(0);
|
|
253
|
+
date.setSeconds(0);
|
|
254
|
+
date.setMinutes(0);
|
|
255
|
+
date.setHours(0);
|
|
256
|
+
|
|
257
|
+
if (year < 0 || year > 10000) {
|
|
258
|
+
return ArgumentError();
|
|
259
|
+
}
|
|
260
|
+
if (year < 1899) { year += 1900; }
|
|
261
|
+
date.setFullYear(year);
|
|
262
|
+
|
|
263
|
+
if (month < 1 || month > 12) {
|
|
264
|
+
return ArgumentError();
|
|
265
|
+
}
|
|
266
|
+
date.setMonth(month - 1);
|
|
267
|
+
|
|
268
|
+
if (day < 1 || day > 31) {
|
|
269
|
+
return ArgumentError();
|
|
270
|
+
}
|
|
271
|
+
date.setDate(day);
|
|
272
|
+
|
|
273
|
+
return { type: ValueType.number, value: UnlotusDate(date.getTime()) };
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
|
|
277
|
+
Today: {
|
|
278
|
+
description: 'Returns current day',
|
|
279
|
+
volatile: true,
|
|
280
|
+
fn: () => {
|
|
281
|
+
const date = new Date();
|
|
282
|
+
date.setMilliseconds(0);
|
|
283
|
+
date.setSeconds(0);
|
|
284
|
+
date.setMinutes(0);
|
|
285
|
+
date.setHours(12);
|
|
286
|
+
return { type: ValueType.number, value: UnlotusDate(date.getTime()) };
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
|
|
290
|
+
IfError: {
|
|
291
|
+
description: 'Returns the original value, or the alternate value if the original value contains an error',
|
|
292
|
+
arguments: [{ name: 'original value', allow_error: true, boxed: true }, { name: 'alternate value' }],
|
|
293
|
+
fn: (ref: UnionValue, value_if_error: unknown = 0): UnionValue => {
|
|
294
|
+
if (ref && ref.type === ValueType.error) {
|
|
295
|
+
return { value: value_if_error, type: GetValueType(value_if_error) } as UnionValue;
|
|
296
|
+
}
|
|
297
|
+
return ref;
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
|
|
301
|
+
IsError: {
|
|
302
|
+
description: 'Checks if another cell contains an error',
|
|
303
|
+
arguments: [{ name: 'reference', allow_error: true, boxed: true }],
|
|
304
|
+
fn: (...args: UnionValue[]): UnionValue => {
|
|
305
|
+
|
|
306
|
+
const values = Utils.FlattenBoxed(args);
|
|
307
|
+
for (const value of values) {
|
|
308
|
+
if (value.type === ValueType.error) {
|
|
309
|
+
return { type: ValueType.boolean, value: true };
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/*
|
|
314
|
+
if (Array.isArray(ref)) {
|
|
315
|
+
const values = Utils.Flatten(ref) as UnionValue[];
|
|
316
|
+
for (const value of values) {
|
|
317
|
+
if (value.type === ValueType.error) {
|
|
318
|
+
return { type: ValueType.boolean, value: true };
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
else if (ref) {
|
|
323
|
+
return { type: ValueType.boolean, value: ref.type === ValueType.error };
|
|
324
|
+
}
|
|
325
|
+
*/
|
|
326
|
+
|
|
327
|
+
return { type: ValueType.boolean, value: false };
|
|
328
|
+
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
Cell: {
|
|
334
|
+
description: 'Returns data about a cell',
|
|
335
|
+
arguments: [
|
|
336
|
+
{ name: 'type', description: 'Type of data to return' },
|
|
337
|
+
{ name: 'reference', description: 'Cell reference', metadata: true },
|
|
338
|
+
],
|
|
339
|
+
|
|
340
|
+
// there's no concept of "structure volatile", and structure events
|
|
341
|
+
// don't trigger recalc, so this is not helpful -- we may need to
|
|
342
|
+
// think about both of those things
|
|
343
|
+
|
|
344
|
+
// volatile: true,
|
|
345
|
+
|
|
346
|
+
fn: Utils.ApplyAsArray2((type: string, reference: UnionValue): UnionValue => {
|
|
347
|
+
|
|
348
|
+
if (!UnionIsMetadata(reference)) {
|
|
349
|
+
return ReferenceError();
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (type) {
|
|
353
|
+
switch (type.toString().toLowerCase()) {
|
|
354
|
+
case 'format':
|
|
355
|
+
return reference.value.format ? // || ReferenceError;
|
|
356
|
+
{ type: ValueType.string, value: reference.value.format } : ReferenceError();
|
|
357
|
+
case 'address':
|
|
358
|
+
return { type: ValueType.string, value: reference.value.address.label.replace(/\$/g, '') };
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return { type: ValueType.error, value: NotImplError.error };
|
|
363
|
+
|
|
364
|
+
}),
|
|
365
|
+
},
|
|
366
|
+
|
|
367
|
+
Year: {
|
|
368
|
+
description: 'Returns year from date',
|
|
369
|
+
arguments: [{
|
|
370
|
+
name: 'date',
|
|
371
|
+
}],
|
|
372
|
+
fn: (source: number): UnionValue => {
|
|
373
|
+
return Box(new Date(LotusDate(source)).getUTCFullYear());
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
Month: {
|
|
379
|
+
description: 'Returns month from date',
|
|
380
|
+
arguments: [{
|
|
381
|
+
name: 'date',
|
|
382
|
+
}],
|
|
383
|
+
fn: (source: number): UnionValue => {
|
|
384
|
+
return Box(new Date(LotusDate(source)).getUTCMonth() + 1); // 0-based
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
Day: {
|
|
390
|
+
description: 'Returns day of month from date',
|
|
391
|
+
arguments: [{
|
|
392
|
+
name: 'date',
|
|
393
|
+
}],
|
|
394
|
+
fn: (source: number): UnionValue => {
|
|
395
|
+
return Box(new Date(LotusDate(source)).getUTCDate());
|
|
396
|
+
},
|
|
397
|
+
},
|
|
398
|
+
|
|
399
|
+
Radians: {
|
|
400
|
+
description: 'Converts degrees to radians',
|
|
401
|
+
arguments: [{ name: 'Degrees', description: 'Angle in degrees' }],
|
|
402
|
+
fn: Utils.ApplyAsArray((degrees: number): UnionValue => {
|
|
403
|
+
return Box(degrees * Math.PI / 180);
|
|
404
|
+
}),
|
|
405
|
+
},
|
|
406
|
+
|
|
407
|
+
Degrees: {
|
|
408
|
+
description: 'Converts radians to degrees',
|
|
409
|
+
arguments: [{ name: 'Radians', description: 'Angle in radians' }],
|
|
410
|
+
fn: Utils.ApplyAsArray((radians: number): UnionValue => {
|
|
411
|
+
return Box(radians / Math.PI * 180);
|
|
412
|
+
}),
|
|
413
|
+
},
|
|
414
|
+
|
|
415
|
+
CountA: {
|
|
416
|
+
description: 'Counts cells that are not empty',
|
|
417
|
+
fn: (...args: unknown[]): UnionValue => {
|
|
418
|
+
return Box(Utils.FlattenUnboxed(args).reduce((a: number, b: unknown) => {
|
|
419
|
+
if (typeof b === 'undefined') return a;
|
|
420
|
+
return a + 1;
|
|
421
|
+
}, 0));
|
|
422
|
+
},
|
|
423
|
+
},
|
|
424
|
+
|
|
425
|
+
Count: {
|
|
426
|
+
description: 'Counts cells that contain numbers',
|
|
427
|
+
fn: (...args: unknown[]): UnionValue => {
|
|
428
|
+
return Box(Utils.FlattenUnboxed(args).reduce((a: number, b: unknown) => {
|
|
429
|
+
if (typeof b === 'number') return a + 1;
|
|
430
|
+
return a;
|
|
431
|
+
}, 0));
|
|
432
|
+
},
|
|
433
|
+
},
|
|
434
|
+
|
|
435
|
+
Or: {
|
|
436
|
+
fn: (...args: unknown[]): UnionValue => {
|
|
437
|
+
let result = false;
|
|
438
|
+
args = Utils.FlattenUnboxed(args);
|
|
439
|
+
for (const arg of args) {
|
|
440
|
+
result = result || !!arg;
|
|
441
|
+
}
|
|
442
|
+
return Box(result);
|
|
443
|
+
},
|
|
444
|
+
},
|
|
445
|
+
|
|
446
|
+
And: {
|
|
447
|
+
fn: (...args: unknown[]): UnionValue => {
|
|
448
|
+
let result = true;
|
|
449
|
+
args = Utils.FlattenUnboxed(args);
|
|
450
|
+
for (const arg of args) {
|
|
451
|
+
result = result && !!arg;
|
|
452
|
+
}
|
|
453
|
+
return Box(result);
|
|
454
|
+
},
|
|
455
|
+
},
|
|
456
|
+
|
|
457
|
+
Not: {
|
|
458
|
+
fn: (...args: unknown[]): UnionValue => {
|
|
459
|
+
if (args.length === 0) {
|
|
460
|
+
return ArgumentError();
|
|
461
|
+
}
|
|
462
|
+
if (args.length === 1) {
|
|
463
|
+
return Box(!args[0]);
|
|
464
|
+
}
|
|
465
|
+
return Box(true);
|
|
466
|
+
}
|
|
467
|
+
},
|
|
468
|
+
|
|
469
|
+
If: {
|
|
470
|
+
arguments: [
|
|
471
|
+
{ name: 'test value', boxed: true },
|
|
472
|
+
{ name: 'value if true', boxed: true, allow_error: true },
|
|
473
|
+
{ name: 'value if false', boxed: true, allow_error: true },
|
|
474
|
+
],
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* should we really have defaults for the t/f paths? not sure what X does
|
|
478
|
+
* @returns
|
|
479
|
+
*/
|
|
480
|
+
fn: (a: UnionValue,
|
|
481
|
+
b: UnionValue = {type: ValueType.boolean, value: true},
|
|
482
|
+
c: UnionValue = {type: ValueType.boolean, value: false}): UnionValue => {
|
|
483
|
+
|
|
484
|
+
const b_array = b.type === ValueType.array;
|
|
485
|
+
const c_array = c.type === ValueType.array;
|
|
486
|
+
|
|
487
|
+
if (a.type === ValueType.array) {
|
|
488
|
+
return {
|
|
489
|
+
type: ValueType.array,
|
|
490
|
+
value: a.value.map((row, x) => row.map((cell, y) => {
|
|
491
|
+
const value = (cell.type === ValueType.string) ?
|
|
492
|
+
(cell.value.toLowerCase() !== 'false' && cell.value.toLowerCase() !== 'f') : !!cell.value;
|
|
493
|
+
return value ? (b_array ? b.value[x][y] : b) : (c_array ? c.value[x][y] : c);
|
|
494
|
+
})) as UnionValue[][],
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const value = a.type === ValueType.string ? // UnionIs.String(a) ?
|
|
499
|
+
(a.value.toLowerCase() !== 'false' && a.value.toLowerCase() !== 'f') : !!a.value;
|
|
500
|
+
|
|
501
|
+
return value ? b : c;
|
|
502
|
+
|
|
503
|
+
},
|
|
504
|
+
},
|
|
505
|
+
|
|
506
|
+
Fact: {
|
|
507
|
+
description: 'Returns the factorial of a number',
|
|
508
|
+
arguments: [
|
|
509
|
+
{ name: 'number' },
|
|
510
|
+
],
|
|
511
|
+
fn: Utils.ApplyAsArray((number: number): UnionValue => {
|
|
512
|
+
number = Math.floor(number);
|
|
513
|
+
let value = 1;
|
|
514
|
+
while (number > 1) {
|
|
515
|
+
value *= number;
|
|
516
|
+
number--;
|
|
517
|
+
}
|
|
518
|
+
return {
|
|
519
|
+
type: ValueType.number,
|
|
520
|
+
value,
|
|
521
|
+
}
|
|
522
|
+
}),
|
|
523
|
+
},
|
|
524
|
+
|
|
525
|
+
Power: {
|
|
526
|
+
description: 'Returns base raised to the given power',
|
|
527
|
+
arguments: [
|
|
528
|
+
{ name: 'base', boxed: true, },
|
|
529
|
+
{ name: 'exponent', boxed: true, }
|
|
530
|
+
],
|
|
531
|
+
fn: Utils.ApplyAsArray2((base: UnionValue, exponent: UnionValue): UnionValue => {
|
|
532
|
+
|
|
533
|
+
/*
|
|
534
|
+
if (base.type === ValueType.number) {
|
|
535
|
+
base = {
|
|
536
|
+
type: ValueType.complex,
|
|
537
|
+
value: { imaginary: 0, real: base.value },
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
*/
|
|
541
|
+
|
|
542
|
+
if (base.type === ValueType.complex || exponent.type === ValueType.complex) {
|
|
543
|
+
|
|
544
|
+
const a = base.type === ValueType.complex ? base.value :
|
|
545
|
+
{ real: base.value || 0, imaginary: 0, };
|
|
546
|
+
const b = exponent.type === ValueType.complex ? exponent.value :
|
|
547
|
+
{ real: exponent.value || 0, imaginary: 0, };
|
|
548
|
+
|
|
549
|
+
const value = ComplexPower(a, b);
|
|
550
|
+
|
|
551
|
+
return ComplexOrReal(value);
|
|
552
|
+
|
|
553
|
+
}
|
|
554
|
+
else {
|
|
555
|
+
const value = Math.pow(base.value, exponent.value);
|
|
556
|
+
if (isNaN(value)) {
|
|
557
|
+
return ValueError();
|
|
558
|
+
}
|
|
559
|
+
return { type: ValueType.number, value };
|
|
560
|
+
// return Box(Math.pow(base.value, exponent.value))
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
}),
|
|
564
|
+
},
|
|
565
|
+
|
|
566
|
+
Mod: {
|
|
567
|
+
fn: Utils.ApplyAsArray2((num: number, divisor: number): UnionValue => {
|
|
568
|
+
if (!divisor) {
|
|
569
|
+
return DivideByZeroError();
|
|
570
|
+
}
|
|
571
|
+
return Box(num % divisor);
|
|
572
|
+
})
|
|
573
|
+
},
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* sort arguments, but ensure we return empty strings to
|
|
577
|
+
* fill up the result array
|
|
578
|
+
*
|
|
579
|
+
* FIXME: instead of boxing all the values, why not pass them in boxed?
|
|
580
|
+
* was this function just written at the wrong time?
|
|
581
|
+
*/
|
|
582
|
+
Sort: {
|
|
583
|
+
arguments: [
|
|
584
|
+
{ name: 'values' }
|
|
585
|
+
],
|
|
586
|
+
fn: (...args: any[]): UnionValue => {
|
|
587
|
+
|
|
588
|
+
args = Utils.FlattenUnboxed(args);
|
|
589
|
+
|
|
590
|
+
if(args.every(test => typeof test === 'number')) {
|
|
591
|
+
args.sort((a, b) => a - b);
|
|
592
|
+
}
|
|
593
|
+
else {
|
|
594
|
+
args.sort(); // lexical
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
return { type: ValueType.array, value: [args.map(value => Box(value))] };
|
|
598
|
+
|
|
599
|
+
},
|
|
600
|
+
},
|
|
601
|
+
|
|
602
|
+
Transpose: {
|
|
603
|
+
description: 'Returns transpose of input matrix',
|
|
604
|
+
arguments: [{name: 'matrix', boxed: true}],
|
|
605
|
+
fn: (mat: UnionValue): UnionValue => {
|
|
606
|
+
|
|
607
|
+
if (mat.type === ValueType.array) {
|
|
608
|
+
return {
|
|
609
|
+
type: ValueType.array,
|
|
610
|
+
value: Utils.Transpose2(mat.value),
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/*
|
|
615
|
+
if (Array.isArray(mat)) {
|
|
616
|
+
return Utils.Transpose2(mat);
|
|
617
|
+
}
|
|
618
|
+
*/
|
|
619
|
+
|
|
620
|
+
return mat;
|
|
621
|
+
}
|
|
622
|
+
},
|
|
623
|
+
|
|
624
|
+
Max: {
|
|
625
|
+
fn: (...args: any[]): UnionValue => {
|
|
626
|
+
return {
|
|
627
|
+
type: ValueType.number,
|
|
628
|
+
value: Math.max.apply(0, Utils.FlattenUnboxed(args).filter(x => typeof x === 'number')),
|
|
629
|
+
};
|
|
630
|
+
},
|
|
631
|
+
},
|
|
632
|
+
|
|
633
|
+
Min: {
|
|
634
|
+
fn: (...args: any[]): UnionValue => {
|
|
635
|
+
return {
|
|
636
|
+
type: ValueType.number,
|
|
637
|
+
value: Math.min.apply(0, Utils.FlattenUnboxed(args).filter(x => typeof x === 'number')),
|
|
638
|
+
};
|
|
639
|
+
},
|
|
640
|
+
},
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
/*
|
|
644
|
+
MMult: {
|
|
645
|
+
description: 'Multiplies two matrices',
|
|
646
|
+
arguments: [{ name: 'Matrix 1'}, { name: 'Matrix 2'}],
|
|
647
|
+
fn: (a, b) => {
|
|
648
|
+
if (!a || !b) return ArgumentError;
|
|
649
|
+
|
|
650
|
+
const a_cols = a.length || 0;
|
|
651
|
+
const a_rows = a[0]?.length || 0;
|
|
652
|
+
|
|
653
|
+
const b_cols = b.length || 0;
|
|
654
|
+
const b_rows = b[0]?.length || 0;
|
|
655
|
+
|
|
656
|
+
if (!a_rows || !b_rows || !a_cols || !b_cols
|
|
657
|
+
|| a_rows !== b_cols || a_cols !== b_rows) return ValueError;
|
|
658
|
+
|
|
659
|
+
const result: number[][] = [];
|
|
660
|
+
|
|
661
|
+
// slightly confusing because we're column-major
|
|
662
|
+
|
|
663
|
+
for (let c = 0; c < b_cols; c++) {
|
|
664
|
+
result[c] = [];
|
|
665
|
+
for (let r = 0; r < a_rows; r++) {
|
|
666
|
+
result[c][r] = 0;
|
|
667
|
+
for (let x = 0; x < a_cols; x++) {
|
|
668
|
+
result[c][r] += a[x][r] * b[c][x];
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
return result;
|
|
673
|
+
|
|
674
|
+
}
|
|
675
|
+
},
|
|
676
|
+
*/
|
|
677
|
+
|
|
678
|
+
SumProduct: {
|
|
679
|
+
description: 'Returns the sum of pairwise products of two or more ranges',
|
|
680
|
+
fn: (...args: any[]): UnionValue => {
|
|
681
|
+
|
|
682
|
+
const flattened = args.map(arg => Utils.FlattenUnboxed(arg));
|
|
683
|
+
const len = Math.max.apply(0, flattened.map(x => x.length));
|
|
684
|
+
|
|
685
|
+
let sum = 0;
|
|
686
|
+
for (let i = 0; i < len; i++) {
|
|
687
|
+
sum += flattened.reduce((a, arg) => {
|
|
688
|
+
return a * (arg[i] || 0);
|
|
689
|
+
}, 1);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
return { type: ValueType.number, value: sum };
|
|
693
|
+
|
|
694
|
+
},
|
|
695
|
+
},
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
*
|
|
699
|
+
* match type:
|
|
700
|
+
*
|
|
701
|
+
* 1: largest value <= target value; assumes table is in ascending order.
|
|
702
|
+
* 0: exact match only.
|
|
703
|
+
* -1: smallest value >= target value; assumes table is in descending order.
|
|
704
|
+
*
|
|
705
|
+
* NOTE that string matches can accept wildcards in Excel, not sure if we
|
|
706
|
+
* necessarily want to support that... how does string matching deal with
|
|
707
|
+
* inequalities?
|
|
708
|
+
* /
|
|
709
|
+
Match: {
|
|
710
|
+
fn: (value: CellValue, table: CellValue[][], match_type: 1|0|-1 = 1) => {
|
|
711
|
+
|
|
712
|
+
const flat = table.reduce((a, row) => ([...a, ...row]), []);
|
|
713
|
+
for (let i = 0; i < flat.length; i++) {
|
|
714
|
+
|
|
715
|
+
const compare = flat[i];
|
|
716
|
+
|
|
717
|
+
console.info("CV", compare, value);
|
|
718
|
+
|
|
719
|
+
// this is true regardless of match type... right?
|
|
720
|
+
if (compare === value) {
|
|
721
|
+
return { type: ValueType.number, value: i + 1 };
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
if ((typeof compare !== 'undefined' && typeof value !== 'undefined') && (
|
|
725
|
+
(match_type === 1 && compare > value) ||
|
|
726
|
+
(match_type === -1 && compare < value))) {
|
|
727
|
+
|
|
728
|
+
if (i === 0 || i === flat.length - 1) {
|
|
729
|
+
return NAError();
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
return { type: ValueType.number, value: i }; // implicit -1
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
}
|
|
736
|
+
return NAError();
|
|
737
|
+
},
|
|
738
|
+
},
|
|
739
|
+
*/
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* FIXME: does not implement inexact matching (what's the algo for
|
|
743
|
+
* that, anyway? nearest? price is right style? what about ties?)
|
|
744
|
+
*/
|
|
745
|
+
VLookup: {
|
|
746
|
+
fn: (value: any, table: any[][], col: number, inexact = true): UnionValue => {
|
|
747
|
+
|
|
748
|
+
col = Math.max(0, col - 1);
|
|
749
|
+
|
|
750
|
+
if (inexact) {
|
|
751
|
+
|
|
752
|
+
let min = Math.abs(value - table[0][0]);
|
|
753
|
+
let result: any = table[col][0];
|
|
754
|
+
|
|
755
|
+
for (let i = 1; i < table[0].length; i++) {
|
|
756
|
+
|
|
757
|
+
const abs = Math.abs(table[0][i] - value);
|
|
758
|
+
|
|
759
|
+
if (abs < min) { // implies first match
|
|
760
|
+
min = abs;
|
|
761
|
+
result = table[col][i];
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
return Box(result);
|
|
766
|
+
|
|
767
|
+
}
|
|
768
|
+
else {
|
|
769
|
+
for (let i = 1; i < table[0].length; i++) {
|
|
770
|
+
if (table[0][i] == value) { // ==
|
|
771
|
+
return table[col][i];
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
return NAError();
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
},
|
|
778
|
+
},
|
|
779
|
+
|
|
780
|
+
Product: {
|
|
781
|
+
arguments: [{boxed: true}],
|
|
782
|
+
fn: (...args: any[]): UnionValue => {
|
|
783
|
+
|
|
784
|
+
let product: Complex = { real: 1, imaginary: 0 };
|
|
785
|
+
|
|
786
|
+
args = Utils.FlattenBoxed(args);
|
|
787
|
+
|
|
788
|
+
for (const arg of args as UnionValue[]) {
|
|
789
|
+
if (arg.type === ValueType.complex) {
|
|
790
|
+
product = ComplexMultply(product, arg.value);
|
|
791
|
+
}
|
|
792
|
+
else if (arg.type === ValueType.number) {
|
|
793
|
+
product.real *= arg.value;
|
|
794
|
+
product.imaginary *= arg.value;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
return ComplexOrReal(product);
|
|
799
|
+
|
|
800
|
+
/*
|
|
801
|
+
return { type: ValueType.number, value: Utils.Flatten(args).reduce((a: number, b: any) => {
|
|
802
|
+
if (typeof b === 'undefined') return a;
|
|
803
|
+
return a * Number(b);
|
|
804
|
+
}, 1) };
|
|
805
|
+
*/
|
|
806
|
+
|
|
807
|
+
},
|
|
808
|
+
},
|
|
809
|
+
|
|
810
|
+
Log: {
|
|
811
|
+
/** default is base 10; allow specific base */
|
|
812
|
+
fn: Utils.ApplyAsArray2((a: number, base = 10): UnionValue => {
|
|
813
|
+
return { type: ValueType.number, value: Math.log(a) / Math.log(base) };
|
|
814
|
+
}),
|
|
815
|
+
},
|
|
816
|
+
|
|
817
|
+
Log10: {
|
|
818
|
+
fn: Utils.ApplyAsArray((a: number): UnionValue => {
|
|
819
|
+
return { type: ValueType.number, value: Math.log(a) / Math.log(10) };
|
|
820
|
+
}),
|
|
821
|
+
},
|
|
822
|
+
|
|
823
|
+
Ln: {
|
|
824
|
+
fn: Utils.ApplyAsArray((a: number): UnionValue => {
|
|
825
|
+
return { type: ValueType.number, value: Math.log(a) };
|
|
826
|
+
}),
|
|
827
|
+
},
|
|
828
|
+
|
|
829
|
+
Round: {
|
|
830
|
+
fn: Utils.ApplyAsArray2((a, digits = 0) => {
|
|
831
|
+
const m = Math.pow(10, digits);
|
|
832
|
+
return {
|
|
833
|
+
type: ValueType.number,
|
|
834
|
+
value: Math.round(m * a) / m,
|
|
835
|
+
};
|
|
836
|
+
}),
|
|
837
|
+
},
|
|
838
|
+
|
|
839
|
+
RoundDown: {
|
|
840
|
+
fn: Utils.ApplyAsArray2((a, digits = 0) => {
|
|
841
|
+
const m = Math.pow(10, digits);
|
|
842
|
+
const positive = a >= 0;
|
|
843
|
+
return {
|
|
844
|
+
type: ValueType.number,
|
|
845
|
+
value: positive ? Math.floor(m * a) / m : Math.ceil(m * a) / m,
|
|
846
|
+
};
|
|
847
|
+
}),
|
|
848
|
+
},
|
|
849
|
+
|
|
850
|
+
/*
|
|
851
|
+
|
|
852
|
+
Round: {
|
|
853
|
+
description: 'Round to a specified number of digits',
|
|
854
|
+
|
|
855
|
+
/ ** round with variable digits * /
|
|
856
|
+
fn: (value: number, digits = 0) => {
|
|
857
|
+
const m = Math.pow(10, digits);
|
|
858
|
+
return Math.round(m * value) / m;
|
|
859
|
+
},
|
|
860
|
+
},
|
|
861
|
+
|
|
862
|
+
RoundDown: {
|
|
863
|
+
/ ** round down with variable digits * /
|
|
864
|
+
fn: (value: number, digits = 0) => {
|
|
865
|
+
digits = Math.max(0, digits);
|
|
866
|
+
const m = Math.pow(10, digits);
|
|
867
|
+
return Math.floor(m * value) / m;
|
|
868
|
+
},
|
|
869
|
+
},
|
|
870
|
+
|
|
871
|
+
|
|
872
|
+
*/
|
|
873
|
+
|
|
874
|
+
|
|
875
|
+
Reverse: {
|
|
876
|
+
arguments: [
|
|
877
|
+
{ boxed: true },
|
|
878
|
+
],
|
|
879
|
+
fn: (a: UnionValue): UnionValue => {
|
|
880
|
+
|
|
881
|
+
/*
|
|
882
|
+
|
|
883
|
+
what is this? this would do anything useful
|
|
884
|
+
...oh I see, it reverses along one axis or the other
|
|
885
|
+
|
|
886
|
+
if ( Array.isArray(a)) {
|
|
887
|
+
if (a.length === 1 ) return [a[0].reverse()];
|
|
888
|
+
return a.reverse();
|
|
889
|
+
}
|
|
890
|
+
*/
|
|
891
|
+
|
|
892
|
+
if (a.type === ValueType.array) {
|
|
893
|
+
if (a.value.length === 1) {
|
|
894
|
+
a.value[0].reverse();
|
|
895
|
+
}
|
|
896
|
+
else {
|
|
897
|
+
a.value.reverse();
|
|
898
|
+
}
|
|
899
|
+
return a;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
return {
|
|
903
|
+
type: ValueType.string,
|
|
904
|
+
value: a.value.toString().split('').reverse().join(''),
|
|
905
|
+
};
|
|
906
|
+
},
|
|
907
|
+
},
|
|
908
|
+
|
|
909
|
+
/**
|
|
910
|
+
* exp was not broken out, but added so we can support complex numbers.
|
|
911
|
+
*/
|
|
912
|
+
Exp: {
|
|
913
|
+
arguments: [
|
|
914
|
+
{ boxed: true },
|
|
915
|
+
],
|
|
916
|
+
fn: Utils.ApplyAsArray((x: UnionValue) => {
|
|
917
|
+
if (x.type === ValueType.complex) {
|
|
918
|
+
const value = ComplexExp(x.value);
|
|
919
|
+
return ComplexOrReal(value);
|
|
920
|
+
}
|
|
921
|
+
return { type: ValueType.number, value: Math.exp(x.value || 0) };
|
|
922
|
+
}),
|
|
923
|
+
},
|
|
924
|
+
|
|
925
|
+
/**
|
|
926
|
+
* abs was already broken out so we could support array application,
|
|
927
|
+
* then updated to support complex numbers.
|
|
928
|
+
*/
|
|
929
|
+
Abs: {
|
|
930
|
+
arguments: [
|
|
931
|
+
{ boxed: true },
|
|
932
|
+
],
|
|
933
|
+
fn: Utils.ApplyAsArray((a: UnionValue) => {
|
|
934
|
+
if (a.type === ValueType.complex) {
|
|
935
|
+
return {
|
|
936
|
+
type: ValueType.number,
|
|
937
|
+
value: Math.sqrt(a.value.real * a.value.real + a.value.imaginary * a.value.imaginary),
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
return { type: ValueType.number, value: Math.abs(a.value || 0) };
|
|
941
|
+
}),
|
|
942
|
+
},
|
|
943
|
+
|
|
944
|
+
Simplify: {
|
|
945
|
+
arguments: [
|
|
946
|
+
{ name: 'value' },
|
|
947
|
+
{ name: 'significant digits' },
|
|
948
|
+
],
|
|
949
|
+
fn: Utils.ApplyAsArray2((value: number, significant_digits = 2): UnionValue => {
|
|
950
|
+
significant_digits = significant_digits || 2;
|
|
951
|
+
if (value === 0) {
|
|
952
|
+
return { type: ValueType.number, value };
|
|
953
|
+
}
|
|
954
|
+
const negative = value < 0 ? -1 : 1;
|
|
955
|
+
value *= negative;
|
|
956
|
+
const x = Math.pow(10, Math.floor(Math.log10(value)) + 1 - significant_digits);
|
|
957
|
+
return {
|
|
958
|
+
type: ValueType.number,
|
|
959
|
+
value: Math.round(value / x) * x * negative
|
|
960
|
+
};
|
|
961
|
+
}),
|
|
962
|
+
},
|
|
963
|
+
|
|
964
|
+
Erf: {
|
|
965
|
+
fn: (a: number): UnionValue => {
|
|
966
|
+
return { type: ValueType.number, value: erf(a) };
|
|
967
|
+
},
|
|
968
|
+
},
|
|
969
|
+
|
|
970
|
+
'NormsInv': {
|
|
971
|
+
|
|
972
|
+
description: 'Inverse of the normal cumulative distribution',
|
|
973
|
+
arguments: [
|
|
974
|
+
{name: 'probability'},
|
|
975
|
+
],
|
|
976
|
+
|
|
977
|
+
fn: (q: number): UnionValue => {
|
|
978
|
+
return {
|
|
979
|
+
type: ValueType.number,
|
|
980
|
+
value: inverse_normal(q),
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
},
|
|
984
|
+
|
|
985
|
+
'Norm.Inv': {
|
|
986
|
+
description: 'Inverse of the normal cumulative distribution',
|
|
987
|
+
arguments: [
|
|
988
|
+
{name: 'probability'},
|
|
989
|
+
{name: 'mean', default: 0},
|
|
990
|
+
{name: 'standard deviation', default: 1},
|
|
991
|
+
],
|
|
992
|
+
xlfn: true,
|
|
993
|
+
fn: (q: number, mean = 0, stdev = 1): UnionValue => {
|
|
994
|
+
return {
|
|
995
|
+
type: ValueType.number,
|
|
996
|
+
value: inverse_normal(q) * stdev + mean,
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
},
|
|
1000
|
+
|
|
1001
|
+
'Norm.Dist': {
|
|
1002
|
+
|
|
1003
|
+
description: 'Cumulative normal distribution',
|
|
1004
|
+
arguments: [
|
|
1005
|
+
{name: 'value'},
|
|
1006
|
+
{name: 'mean', default: 0},
|
|
1007
|
+
{name: 'standard deviation', default: 1},
|
|
1008
|
+
{name: 'cumulative', default: true},
|
|
1009
|
+
],
|
|
1010
|
+
|
|
1011
|
+
// this does need xlfn but it also requires four parameters
|
|
1012
|
+
// (we have three and they are not required).
|
|
1013
|
+
|
|
1014
|
+
xlfn: true,
|
|
1015
|
+
|
|
1016
|
+
fn: (x: number, mean = 0, stdev = 1, cumulative = true): UnionValue => {
|
|
1017
|
+
|
|
1018
|
+
let value = 0;
|
|
1019
|
+
|
|
1020
|
+
if (cumulative) {
|
|
1021
|
+
const sign = (x < mean) ? -1 : 1;
|
|
1022
|
+
value = 0.5 * (1.0 + sign * erf((Math.abs(x - mean)) / (stdev * Math.sqrt(2))));
|
|
1023
|
+
}
|
|
1024
|
+
else {
|
|
1025
|
+
value = Math.exp(-1/2 * Math.pow((x - mean) / stdev, 2)) / (stdev * sqrt2pi);
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
return {
|
|
1029
|
+
type: ValueType.number,
|
|
1030
|
+
value,
|
|
1031
|
+
};
|
|
1032
|
+
|
|
1033
|
+
},
|
|
1034
|
+
},
|
|
1035
|
+
|
|
1036
|
+
Sqrt: {
|
|
1037
|
+
description: 'Returns the square root of the argument',
|
|
1038
|
+
arguments: [
|
|
1039
|
+
{boxed: true},
|
|
1040
|
+
],
|
|
1041
|
+
fn: Utils.ApplyAsArray((ref: UnionValue): UnionValue => {
|
|
1042
|
+
|
|
1043
|
+
// little bit torn on this. what should sqrt(-1) return? a complex
|
|
1044
|
+
// number, or NaN? or should we control that with a flag?
|
|
1045
|
+
|
|
1046
|
+
// UPDATE: now optional, see AltFunctionLibrary
|
|
1047
|
+
|
|
1048
|
+
if (ref.type === ValueType.complex) {
|
|
1049
|
+
const value = ComplexPower(ref.value, {real: 0.5, imaginary: 0});
|
|
1050
|
+
return ComplexOrReal(value);
|
|
1051
|
+
}
|
|
1052
|
+
else if (ref.type === ValueType.undefined || !ref.value) {
|
|
1053
|
+
return {
|
|
1054
|
+
type: ValueType.number, value: 0,
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
/*
|
|
1058
|
+
else if (ref.type === ValueType.number && ref.value < 0) {
|
|
1059
|
+
const value = ComplexPower({real: ref.value, imaginary: 0}, {real: 0.5, imaginary: 0});
|
|
1060
|
+
return {
|
|
1061
|
+
type: ValueType.complex,
|
|
1062
|
+
value,
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
*/
|
|
1066
|
+
else {
|
|
1067
|
+
const value = Math.sqrt(ref.value);
|
|
1068
|
+
if (isNaN(value)) {
|
|
1069
|
+
return ValueError();
|
|
1070
|
+
}
|
|
1071
|
+
return { type: ValueType.number, value };
|
|
1072
|
+
}
|
|
1073
|
+
}),
|
|
1074
|
+
},
|
|
1075
|
+
|
|
1076
|
+
HexToDec: {
|
|
1077
|
+
arguments: [{ description: 'hexadecimal string' }],
|
|
1078
|
+
fn: (hex: string): UnionValue => {
|
|
1079
|
+
return { type: ValueType.number, value: parseInt(hex, 16) };
|
|
1080
|
+
},
|
|
1081
|
+
},
|
|
1082
|
+
|
|
1083
|
+
DecToHex: {
|
|
1084
|
+
arguments: [{ description: 'number' }],
|
|
1085
|
+
fn: (num: number): UnionValue => {
|
|
1086
|
+
return { type: ValueType.string, value: num.toString(16) };
|
|
1087
|
+
},
|
|
1088
|
+
},
|
|
1089
|
+
|
|
1090
|
+
Checkbox: {
|
|
1091
|
+
arguments: [
|
|
1092
|
+
{ name: 'checked' },
|
|
1093
|
+
],
|
|
1094
|
+
click: ClickCheckbox,
|
|
1095
|
+
render: RenderCheckbox,
|
|
1096
|
+
fn: (checked: boolean): UnionValue => {
|
|
1097
|
+
return { value: !!checked, type: ValueType.boolean, }
|
|
1098
|
+
},
|
|
1099
|
+
},
|
|
1100
|
+
|
|
1101
|
+
'Sparkline.Column': {
|
|
1102
|
+
arguments: [
|
|
1103
|
+
{name: 'data' },
|
|
1104
|
+
{name: 'color'},
|
|
1105
|
+
{name: 'negative color'}],
|
|
1106
|
+
render: (options: RenderFunctionOptions): RenderFunctionResult => {
|
|
1107
|
+
Sparkline.RenderColumn(options.width, options.height, options.context, options.cell, options.style);
|
|
1108
|
+
return { handled: true }; // painted
|
|
1109
|
+
},
|
|
1110
|
+
fn: (...args: unknown[]): UnionValue => {
|
|
1111
|
+
return { type: ValueType.object, value: args, key: 'sparkline-data' };
|
|
1112
|
+
},
|
|
1113
|
+
},
|
|
1114
|
+
|
|
1115
|
+
'Sparkline.Line': {
|
|
1116
|
+
arguments: [
|
|
1117
|
+
{name: 'data'},
|
|
1118
|
+
{name: 'color'},
|
|
1119
|
+
{name: 'line width'},
|
|
1120
|
+
],
|
|
1121
|
+
render: (options: RenderFunctionOptions): RenderFunctionResult => {
|
|
1122
|
+
Sparkline.RenderLine(options.width, options.height, options.context, options.cell, options.style);
|
|
1123
|
+
return { handled: true }; // painted
|
|
1124
|
+
},
|
|
1125
|
+
fn: (...args: unknown[]): UnionValue => {
|
|
1126
|
+
return { type: ValueType.object, value: args, key: 'sparkline-data' };
|
|
1127
|
+
},
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
};
|
|
1131
|
+
|
|
1132
|
+
// alias
|
|
1133
|
+
|
|
1134
|
+
// add functions from Math (intrinsic), unless the name overlaps
|
|
1135
|
+
// with something already in there
|
|
1136
|
+
|
|
1137
|
+
// we need to construct a separate map to match icase (this is now
|
|
1138
|
+
// even more useful since we have a separate section for aliases)
|
|
1139
|
+
|
|
1140
|
+
const name_map: {[index: string]: string} = {};
|
|
1141
|
+
|
|
1142
|
+
for (const key of Object.keys(BaseFunctionLibrary)) {
|
|
1143
|
+
name_map[key.toLowerCase()] = key;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
// block these names from auto-import from Math
|
|
1147
|
+
|
|
1148
|
+
const block_list = [
|
|
1149
|
+
'pow',
|
|
1150
|
+
];
|
|
1151
|
+
|
|
1152
|
+
const block_map: Record<string, string> = {};
|
|
1153
|
+
for (const entry of block_list) {
|
|
1154
|
+
block_map[entry.toLowerCase()] = entry;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
for (const name of Object.getOwnPropertyNames(Math)) {
|
|
1158
|
+
|
|
1159
|
+
// check if it exists (we have already registered something
|
|
1160
|
+
// with the same name) -- don't override existing
|
|
1161
|
+
|
|
1162
|
+
const lc = name.toLowerCase();
|
|
1163
|
+
|
|
1164
|
+
if (name_map[lc]) { continue; }
|
|
1165
|
+
|
|
1166
|
+
// also explicitly block some names we don't want to include (pow vs. power, etc)
|
|
1167
|
+
|
|
1168
|
+
if (block_map[lc]) { continue; }
|
|
1169
|
+
|
|
1170
|
+
const descriptor = Object.getOwnPropertyDescriptor(Math, name);
|
|
1171
|
+
if (!descriptor) { continue; }
|
|
1172
|
+
|
|
1173
|
+
const value = descriptor.value;
|
|
1174
|
+
const type = typeof (value);
|
|
1175
|
+
|
|
1176
|
+
switch (type) {
|
|
1177
|
+
case 'number':
|
|
1178
|
+
// console.info("MATH CONSTANT", name);
|
|
1179
|
+
BaseFunctionLibrary[name] = {
|
|
1180
|
+
fn: () => {
|
|
1181
|
+
return { type: ValueType.number, value }
|
|
1182
|
+
},
|
|
1183
|
+
category: ['Math Functions'],
|
|
1184
|
+
};
|
|
1185
|
+
break;
|
|
1186
|
+
|
|
1187
|
+
case 'function':
|
|
1188
|
+
// console.info("MATH FUNC", name);
|
|
1189
|
+
BaseFunctionLibrary[name] = {
|
|
1190
|
+
fn: (...args: any) => {
|
|
1191
|
+
return Box(value(...args));
|
|
1192
|
+
},
|
|
1193
|
+
category: ['Math Functions'],
|
|
1194
|
+
};
|
|
1195
|
+
break;
|
|
1196
|
+
|
|
1197
|
+
default:
|
|
1198
|
+
console.info('unexpected type:', type, name);
|
|
1199
|
+
break;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
// IE11: patch log10 function // FIXME: is this necessary anymore?
|
|
1205
|
+
|
|
1206
|
+
if (!Math.log10) {
|
|
1207
|
+
Math.log10 = (a) => Math.log(a) / Math.log(10);
|
|
1208
|
+
/*
|
|
1209
|
+
BaseFunctionLibrary.log10 = {
|
|
1210
|
+
fn: (x) => Math.log(x) / Math.log(10),
|
|
1211
|
+
category: ['Math Functions'],
|
|
1212
|
+
};
|
|
1213
|
+
*/
|
|
1214
|
+
}
|