@trebco/treb 37.0.0 → 37.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/package.json +119 -0
- package/build/treb-base-types/src/api_types.d.ts +11 -0
- package/build/treb-base-types/src/api_types.js +22 -0
- package/build/treb-base-types/src/api_types.js.map +1 -0
- package/build/treb-base-types/src/area-utils.d.ts +9 -0
- package/build/treb-base-types/src/area-utils.js +50 -0
- package/build/treb-base-types/src/area-utils.js.map +1 -0
- package/build/treb-base-types/src/area.d.ts +182 -0
- package/build/treb-base-types/src/area.js +715 -0
- package/build/treb-base-types/src/area.js.map +1 -0
- package/build/treb-base-types/src/basic_types.d.ts +20 -0
- package/build/treb-base-types/src/basic_types.js +22 -0
- package/build/treb-base-types/src/basic_types.js.map +1 -0
- package/build/treb-base-types/src/cell.d.ts +167 -0
- package/build/treb-base-types/src/cell.js +432 -0
- package/build/treb-base-types/src/cell.js.map +1 -0
- package/build/treb-base-types/src/cells.d.ts +251 -0
- package/build/treb-base-types/src/cells.js +1136 -0
- package/build/treb-base-types/src/cells.js.map +1 -0
- package/build/treb-base-types/src/color.d.ts +35 -0
- package/build/treb-base-types/src/color.js +162 -0
- package/build/treb-base-types/src/color.js.map +1 -0
- package/build/treb-base-types/src/dom-utilities.d.ts +70 -0
- package/build/treb-base-types/src/dom-utilities.js +144 -0
- package/build/treb-base-types/src/dom-utilities.js.map +1 -0
- package/build/treb-base-types/src/evaluate-options.d.ts +35 -0
- package/build/treb-base-types/src/evaluate-options.js +22 -0
- package/build/treb-base-types/src/evaluate-options.js.map +1 -0
- package/build/treb-base-types/src/font-stack.d.ts +37 -0
- package/build/treb-base-types/src/font-stack.js +93 -0
- package/build/treb-base-types/src/font-stack.js.map +1 -0
- package/build/treb-base-types/src/gradient.d.ts +18 -0
- package/build/treb-base-types/src/gradient.js +86 -0
- package/build/treb-base-types/src/gradient.js.map +1 -0
- package/build/treb-base-types/src/import.d.ts +48 -0
- package/build/treb-base-types/src/import.js +22 -0
- package/build/treb-base-types/src/import.js.map +1 -0
- package/build/treb-base-types/src/index-standalone.d.ts +6 -0
- package/build/treb-base-types/src/index-standalone.js +27 -0
- package/build/treb-base-types/src/index-standalone.js.map +1 -0
- package/build/treb-base-types/src/index.d.ts +22 -0
- package/build/treb-base-types/src/index.js +45 -0
- package/build/treb-base-types/src/index.js.map +1 -0
- package/build/treb-base-types/src/layout.d.ts +22 -0
- package/build/treb-base-types/src/layout.js +22 -0
- package/build/treb-base-types/src/layout.js.map +1 -0
- package/build/treb-base-types/src/localization.d.ts +37 -0
- package/build/treb-base-types/src/localization.js +157 -0
- package/build/treb-base-types/src/localization.js.map +1 -0
- package/build/treb-base-types/src/rectangle.d.ts +51 -0
- package/build/treb-base-types/src/rectangle.js +123 -0
- package/build/treb-base-types/src/rectangle.js.map +1 -0
- package/build/treb-base-types/src/render_text.d.ts +34 -0
- package/build/treb-base-types/src/render_text.js +22 -0
- package/build/treb-base-types/src/render_text.js.map +1 -0
- package/build/treb-base-types/src/style.d.ts +214 -0
- package/build/treb-base-types/src/style.js +373 -0
- package/build/treb-base-types/src/style.js.map +1 -0
- package/build/treb-base-types/src/table.d.ts +58 -0
- package/build/treb-base-types/src/table.js +27 -0
- package/build/treb-base-types/src/table.js.map +1 -0
- package/build/treb-base-types/src/text_part.d.ts +26 -0
- package/build/treb-base-types/src/text_part.js +47 -0
- package/build/treb-base-types/src/text_part.js.map +1 -0
- package/build/treb-base-types/src/theme.d.ts +120 -0
- package/build/treb-base-types/src/theme.js +460 -0
- package/build/treb-base-types/src/theme.js.map +1 -0
- package/build/treb-base-types/src/union.d.ts +73 -0
- package/build/treb-base-types/src/union.js +61 -0
- package/build/treb-base-types/src/union.js.map +1 -0
- package/build/treb-base-types/src/value-type.d.ts +86 -0
- package/build/treb-base-types/src/value-type.js +168 -0
- package/build/treb-base-types/src/value-type.js.map +1 -0
- package/build/treb-base-types/src/worker-proxy.d.ts +95 -0
- package/build/treb-base-types/src/worker-proxy.js +221 -0
- package/build/treb-base-types/src/worker-proxy.js.map +1 -0
- package/build/treb-calculator/src/calculator.d.ts +249 -0
- package/build/treb-calculator/src/calculator.js +2755 -0
- package/build/treb-calculator/src/calculator.js.map +1 -0
- package/build/treb-calculator/src/complex-math.d.ts +75 -0
- package/build/treb-calculator/src/complex-math.js +559 -0
- package/build/treb-calculator/src/complex-math.js.map +1 -0
- package/build/treb-calculator/src/dag/array-vertex.d.ts +71 -0
- package/build/treb-calculator/src/dag/array-vertex.js +156 -0
- package/build/treb-calculator/src/dag/array-vertex.js.map +1 -0
- package/build/treb-calculator/src/dag/calculation_leaf_vertex.d.ts +48 -0
- package/build/treb-calculator/src/dag/calculation_leaf_vertex.js +84 -0
- package/build/treb-calculator/src/dag/calculation_leaf_vertex.js.map +1 -0
- package/build/treb-calculator/src/dag/graph.d.ts +134 -0
- package/build/treb-calculator/src/dag/graph.js +842 -0
- package/build/treb-calculator/src/dag/graph.js.map +1 -0
- package/build/treb-calculator/src/dag/spreadsheet_vertex.d.ts +58 -0
- package/build/treb-calculator/src/dag/spreadsheet_vertex.js +232 -0
- package/build/treb-calculator/src/dag/spreadsheet_vertex.js.map +1 -0
- package/build/treb-calculator/src/dag/spreadsheet_vertex_base.d.ts +20 -0
- package/build/treb-calculator/src/dag/spreadsheet_vertex_base.js +25 -0
- package/build/treb-calculator/src/dag/spreadsheet_vertex_base.js.map +1 -0
- package/build/treb-calculator/src/dag/state_leaf_vertex.d.ts +43 -0
- package/build/treb-calculator/src/dag/state_leaf_vertex.js +81 -0
- package/build/treb-calculator/src/dag/state_leaf_vertex.js.map +1 -0
- package/build/treb-calculator/src/dag/vertex.d.ts +71 -0
- package/build/treb-calculator/src/dag/vertex.js +274 -0
- package/build/treb-calculator/src/dag/vertex.js.map +1 -0
- package/build/treb-calculator/src/descriptors.d.ts +189 -0
- package/build/treb-calculator/src/descriptors.js +22 -0
- package/build/treb-calculator/src/descriptors.js.map +1 -0
- package/build/treb-calculator/src/expression-calculator.d.ts +127 -0
- package/build/treb-calculator/src/expression-calculator.js +1033 -0
- package/build/treb-calculator/src/expression-calculator.js.map +1 -0
- package/build/treb-calculator/src/function-error.d.ts +35 -0
- package/build/treb-calculator/src/function-error.js +85 -0
- package/build/treb-calculator/src/function-error.js.map +1 -0
- package/build/treb-calculator/src/function-library.d.ts +22 -0
- package/build/treb-calculator/src/function-library.js +96 -0
- package/build/treb-calculator/src/function-library.js.map +1 -0
- package/build/treb-calculator/src/functions/base-functions.d.ts +7 -0
- package/build/treb-calculator/src/functions/base-functions.js +2611 -0
- package/build/treb-calculator/src/functions/base-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/beta.d.ts +17 -0
- package/build/treb-calculator/src/functions/beta.js +201 -0
- package/build/treb-calculator/src/functions/beta.js.map +1 -0
- package/build/treb-calculator/src/functions/checkbox.d.ts +3 -0
- package/build/treb-calculator/src/functions/checkbox.js +128 -0
- package/build/treb-calculator/src/functions/checkbox.js.map +1 -0
- package/build/treb-calculator/src/functions/complex-functions.d.ts +2 -0
- package/build/treb-calculator/src/functions/complex-functions.js +217 -0
- package/build/treb-calculator/src/functions/complex-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/date-utils.d.ts +3 -0
- package/build/treb-calculator/src/functions/date-utils.js +59 -0
- package/build/treb-calculator/src/functions/date-utils.js.map +1 -0
- package/build/treb-calculator/src/functions/finance-functions.d.ts +2 -0
- package/build/treb-calculator/src/functions/finance-functions.js +547 -0
- package/build/treb-calculator/src/functions/finance-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/fp.d.ts +2 -0
- package/build/treb-calculator/src/functions/fp.js +463 -0
- package/build/treb-calculator/src/functions/fp.js.map +1 -0
- package/build/treb-calculator/src/functions/function-utilities.d.ts +2 -0
- package/build/treb-calculator/src/functions/function-utilities.js +36 -0
- package/build/treb-calculator/src/functions/function-utilities.js.map +1 -0
- package/build/treb-calculator/src/functions/gamma.d.ts +20 -0
- package/build/treb-calculator/src/functions/gamma.js +142 -0
- package/build/treb-calculator/src/functions/gamma.js.map +1 -0
- package/build/treb-calculator/src/functions/information-functions.d.ts +2 -0
- package/build/treb-calculator/src/functions/information-functions.js +71 -0
- package/build/treb-calculator/src/functions/information-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/lambda-functions.d.ts +2 -0
- package/build/treb-calculator/src/functions/lambda-functions.js +85 -0
- package/build/treb-calculator/src/functions/lambda-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/matrix-functions.d.ts +2 -0
- package/build/treb-calculator/src/functions/matrix-functions.js +144 -0
- package/build/treb-calculator/src/functions/matrix-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/normal.d.ts +2 -0
- package/build/treb-calculator/src/functions/normal.js +32 -0
- package/build/treb-calculator/src/functions/normal.js.map +1 -0
- package/build/treb-calculator/src/functions/regex-functions.d.ts +2 -0
- package/build/treb-calculator/src/functions/regex-functions.js +188 -0
- package/build/treb-calculator/src/functions/regex-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/sparkline.d.ts +37 -0
- package/build/treb-calculator/src/functions/sparkline.js +264 -0
- package/build/treb-calculator/src/functions/sparkline.js.map +1 -0
- package/build/treb-calculator/src/functions/statistics-functions.d.ts +6 -0
- package/build/treb-calculator/src/functions/statistics-functions.js +989 -0
- package/build/treb-calculator/src/functions/statistics-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/students-t.d.ts +3 -0
- package/build/treb-calculator/src/functions/students-t.js +64 -0
- package/build/treb-calculator/src/functions/students-t.js.map +1 -0
- package/build/treb-calculator/src/functions/text-functions.d.ts +3 -0
- package/build/treb-calculator/src/functions/text-functions.js +320 -0
- package/build/treb-calculator/src/functions/text-functions.js.map +1 -0
- package/build/treb-calculator/src/index.d.ts +2 -0
- package/build/treb-calculator/src/index.js +22 -0
- package/build/treb-calculator/src/index.js.map +1 -0
- package/build/treb-calculator/src/notifier-types.d.ts +26 -0
- package/build/treb-calculator/src/notifier-types.js +22 -0
- package/build/treb-calculator/src/notifier-types.js.map +1 -0
- package/build/treb-calculator/src/primitives.d.ts +15 -0
- package/build/treb-calculator/src/primitives.js +398 -0
- package/build/treb-calculator/src/primitives.js.map +1 -0
- package/build/treb-calculator/src/utilities.d.ts +68 -0
- package/build/treb-calculator/src/utilities.js +324 -0
- package/build/treb-calculator/src/utilities.js.map +1 -0
- package/build/treb-charts/src/chart-functions.d.ts +8 -0
- package/build/treb-charts/src/chart-functions.js +209 -0
- package/build/treb-charts/src/chart-functions.js.map +1 -0
- package/build/treb-charts/src/chart-types.d.ts +233 -0
- package/build/treb-charts/src/chart-types.js +57 -0
- package/build/treb-charts/src/chart-types.js.map +1 -0
- package/build/treb-charts/src/chart-utils.d.ts +106 -0
- package/build/treb-charts/src/chart-utils.js +1060 -0
- package/build/treb-charts/src/chart-utils.js.map +1 -0
- package/build/treb-charts/src/chart.d.ts +23 -0
- package/build/treb-charts/src/chart.js +94 -0
- package/build/treb-charts/src/chart.js.map +1 -0
- package/build/treb-charts/src/default-chart-renderer.d.ts +16 -0
- package/build/treb-charts/src/default-chart-renderer.js +533 -0
- package/build/treb-charts/src/default-chart-renderer.js.map +1 -0
- package/build/treb-charts/src/index.d.ts +5 -0
- package/build/treb-charts/src/index.js +24 -0
- package/build/treb-charts/src/index.js.map +1 -0
- package/build/treb-charts/src/main.d.ts +1 -0
- package/build/treb-charts/src/main.js +34 -0
- package/build/treb-charts/src/main.js.map +1 -0
- package/build/treb-charts/src/quicksort.d.ts +1 -0
- package/build/treb-charts/src/quicksort.js +49 -0
- package/build/treb-charts/src/quicksort.js.map +1 -0
- package/build/treb-charts/src/rectangle.d.ts +18 -0
- package/build/treb-charts/src/rectangle.js +41 -0
- package/build/treb-charts/src/rectangle.js.map +1 -0
- package/build/treb-charts/src/renderer-type.d.ts +24 -0
- package/build/treb-charts/src/renderer-type.js +22 -0
- package/build/treb-charts/src/renderer-type.js.map +1 -0
- package/build/treb-charts/src/renderer.d.ts +127 -0
- package/build/treb-charts/src/renderer.js +1518 -0
- package/build/treb-charts/src/renderer.js.map +1 -0
- package/build/treb-charts/src/util.d.ts +18 -0
- package/build/treb-charts/src/util.js +71 -0
- package/build/treb-charts/src/util.js.map +1 -0
- package/build/treb-data-model/src/annotation.d.ts +167 -0
- package/build/treb-data-model/src/annotation.js +120 -0
- package/build/treb-data-model/src/annotation.js.map +1 -0
- package/build/treb-data-model/src/conditional_format.d.ts +155 -0
- package/build/treb-data-model/src/conditional_format.js +62 -0
- package/build/treb-data-model/src/conditional_format.js.map +1 -0
- package/build/treb-data-model/src/data-validation.d.ts +28 -0
- package/build/treb-data-model/src/data-validation.js +22 -0
- package/build/treb-data-model/src/data-validation.js.map +1 -0
- package/build/treb-data-model/src/data_model.d.ts +173 -0
- package/build/treb-data-model/src/data_model.js +637 -0
- package/build/treb-data-model/src/data_model.js.map +1 -0
- package/build/treb-data-model/src/index.d.ts +13 -0
- package/build/treb-data-model/src/index.js +28 -0
- package/build/treb-data-model/src/index.js.map +1 -0
- package/build/treb-data-model/src/language-model.d.ts +22 -0
- package/build/treb-data-model/src/language-model.js +22 -0
- package/build/treb-data-model/src/language-model.js.map +1 -0
- package/build/treb-data-model/src/named.d.ts +124 -0
- package/build/treb-data-model/src/named.js +372 -0
- package/build/treb-data-model/src/named.js.map +1 -0
- package/build/treb-data-model/src/serialize_options.d.ts +49 -0
- package/build/treb-data-model/src/serialize_options.js +22 -0
- package/build/treb-data-model/src/serialize_options.js.map +1 -0
- package/build/treb-data-model/src/sheet.d.ts +499 -0
- package/build/treb-data-model/src/sheet.js +2904 -0
- package/build/treb-data-model/src/sheet.js.map +1 -0
- package/build/treb-data-model/src/sheet_collection.d.ts +58 -0
- package/build/treb-data-model/src/sheet_collection.js +112 -0
- package/build/treb-data-model/src/sheet_collection.js.map +1 -0
- package/build/treb-data-model/src/sheet_selection.d.ts +42 -0
- package/build/treb-data-model/src/sheet_selection.js +39 -0
- package/build/treb-data-model/src/sheet_selection.js.map +1 -0
- package/build/treb-data-model/src/sheet_types.d.ts +104 -0
- package/build/treb-data-model/src/sheet_types.js +22 -0
- package/build/treb-data-model/src/sheet_types.js.map +1 -0
- package/build/treb-data-model/src/types.d.ts +59 -0
- package/build/treb-data-model/src/types.js +22 -0
- package/build/treb-data-model/src/types.js.map +1 -0
- package/build/treb-embed/src/custom-element/spreadsheet-constructor.d.ts +75 -0
- package/build/treb-embed/src/custom-element/spreadsheet-constructor.js +1144 -0
- package/build/treb-embed/src/custom-element/spreadsheet-constructor.js.map +1 -0
- package/build/treb-embed/src/custom-element/treb-global.d.ts +36 -0
- package/build/treb-embed/src/custom-element/treb-global.js +64 -0
- package/build/treb-embed/src/custom-element/treb-global.js.map +1 -0
- package/build/treb-embed/src/custom-element/treb-spreadsheet-element.d.ts +1 -0
- package/build/treb-embed/src/custom-element/treb-spreadsheet-element.js +61 -0
- package/build/treb-embed/src/custom-element/treb-spreadsheet-element.js.map +1 -0
- package/build/treb-embed/src/embedded-spreadsheet.d.ts +1358 -0
- package/build/treb-embed/src/embedded-spreadsheet.js +5205 -0
- package/build/treb-embed/src/embedded-spreadsheet.js.map +1 -0
- package/build/treb-embed/src/index.d.ts +12 -0
- package/build/treb-embed/src/index.js +34 -0
- package/build/treb-embed/src/index.js.map +1 -0
- package/build/treb-embed/src/options.d.ts +266 -0
- package/build/treb-embed/src/options.js +56 -0
- package/build/treb-embed/src/options.js.map +1 -0
- package/build/treb-embed/src/plugin.d.ts +9 -0
- package/build/treb-embed/src/plugin.js +22 -0
- package/build/treb-embed/src/plugin.js.map +1 -0
- package/build/treb-embed/src/progress-dialog.d.ts +49 -0
- package/build/treb-embed/src/progress-dialog.js +178 -0
- package/build/treb-embed/src/progress-dialog.js.map +1 -0
- package/build/treb-embed/src/selection-state.d.ts +15 -0
- package/build/treb-embed/src/selection-state.js +22 -0
- package/build/treb-embed/src/selection-state.js.map +1 -0
- package/build/treb-embed/src/spinner.d.ts +8 -0
- package/build/treb-embed/src/spinner.js +40 -0
- package/build/treb-embed/src/spinner.js.map +1 -0
- package/build/treb-embed/src/toolbar-message.d.ts +72 -0
- package/build/treb-embed/src/toolbar-message.js +22 -0
- package/build/treb-embed/src/toolbar-message.js.map +1 -0
- package/build/treb-embed/src/types.d.ts +185 -0
- package/build/treb-embed/src/types.js +45 -0
- package/build/treb-embed/src/types.js.map +1 -0
- package/build/treb-embed/tsconfig.tsbuildinfo +1 -0
- package/build/treb-export/src/address-type.d.ts +34 -0
- package/build/treb-export/src/address-type.js +53 -0
- package/build/treb-export/src/address-type.js.map +1 -0
- package/build/treb-export/src/base-template.d.ts +1 -0
- package/build/treb-export/src/base-template.js +22 -0
- package/build/treb-export/src/base-template.js.map +1 -0
- package/build/treb-export/src/column-width.d.ts +2 -0
- package/build/treb-export/src/column-width.js +80 -0
- package/build/treb-export/src/column-width.js.map +1 -0
- package/build/treb-export/src/drawing/bubble-chart-template.d.ts +514 -0
- package/build/treb-export/src/drawing/bubble-chart-template.js +544 -0
- package/build/treb-export/src/drawing/bubble-chart-template.js.map +1 -0
- package/build/treb-export/src/drawing/chart-template-components2.d.ts +365 -0
- package/build/treb-export/src/drawing/chart-template-components2.js +386 -0
- package/build/treb-export/src/drawing/chart-template-components2.js.map +1 -0
- package/build/treb-export/src/drawing/chart.d.ts +26 -0
- package/build/treb-export/src/drawing/chart.js +247 -0
- package/build/treb-export/src/drawing/chart.js.map +1 -0
- package/build/treb-export/src/drawing/column-chart-template2.d.ts +490 -0
- package/build/treb-export/src/drawing/column-chart-template2.js +518 -0
- package/build/treb-export/src/drawing/column-chart-template2.js.map +1 -0
- package/build/treb-export/src/drawing/donut-chart-template2.d.ts +272 -0
- package/build/treb-export/src/drawing/donut-chart-template2.js +293 -0
- package/build/treb-export/src/drawing/donut-chart-template2.js.map +1 -0
- package/build/treb-export/src/drawing/drawing.d.ts +49 -0
- package/build/treb-export/src/drawing/drawing.js +193 -0
- package/build/treb-export/src/drawing/drawing.js.map +1 -0
- package/build/treb-export/src/drawing/embedded-image.d.ts +12 -0
- package/build/treb-export/src/drawing/embedded-image.js +54 -0
- package/build/treb-export/src/drawing/embedded-image.js.map +1 -0
- package/build/treb-export/src/drawing/scatter-chart-template2.d.ts +520 -0
- package/build/treb-export/src/drawing/scatter-chart-template2.js +551 -0
- package/build/treb-export/src/drawing/scatter-chart-template2.js.map +1 -0
- package/build/treb-export/src/export.d.ts +72 -0
- package/build/treb-export/src/export.js +2039 -0
- package/build/treb-export/src/export.js.map +1 -0
- package/build/treb-export/src/import-export-messages.d.ts +31 -0
- package/build/treb-export/src/import-export-messages.js +22 -0
- package/build/treb-export/src/import-export-messages.js.map +1 -0
- package/build/treb-export/src/import.d.ts +33 -0
- package/build/treb-export/src/import.js +1258 -0
- package/build/treb-export/src/import.js.map +1 -0
- package/build/treb-export/src/index.worker.d.ts +1 -0
- package/build/treb-export/src/index.worker.js +93 -0
- package/build/treb-export/src/index.worker.js.map +1 -0
- package/build/treb-export/src/metadata.d.ts +51 -0
- package/build/treb-export/src/metadata.js +153 -0
- package/build/treb-export/src/metadata.js.map +1 -0
- package/build/treb-export/src/ooxml.d.ts +7 -0
- package/build/treb-export/src/ooxml.js +41 -0
- package/build/treb-export/src/ooxml.js.map +1 -0
- package/build/treb-export/src/relationship.d.ts +8 -0
- package/build/treb-export/src/relationship.js +27 -0
- package/build/treb-export/src/relationship.js.map +1 -0
- package/build/treb-export/src/shared-strings.d.ts +11 -0
- package/build/treb-export/src/shared-strings.js +105 -0
- package/build/treb-export/src/shared-strings.js.map +1 -0
- package/build/treb-export/src/template-2.d.ts +1 -0
- package/build/treb-export/src/template-2.js +22 -0
- package/build/treb-export/src/template-2.js.map +1 -0
- package/build/treb-export/src/unescape_xml.d.ts +1 -0
- package/build/treb-export/src/unescape_xml.js +61 -0
- package/build/treb-export/src/unescape_xml.js.map +1 -0
- package/build/treb-export/src/workbook-sheet.d.ts +75 -0
- package/build/treb-export/src/workbook-sheet.js +128 -0
- package/build/treb-export/src/workbook-sheet.js.map +1 -0
- package/build/treb-export/src/workbook-style.d.ts +110 -0
- package/build/treb-export/src/workbook-style.js +1134 -0
- package/build/treb-export/src/workbook-style.js.map +1 -0
- package/build/treb-export/src/workbook-theme.d.ts +13 -0
- package/build/treb-export/src/workbook-theme.js +85 -0
- package/build/treb-export/src/workbook-theme.js.map +1 -0
- package/build/treb-export/src/workbook.d.ts +123 -0
- package/build/treb-export/src/workbook.js +644 -0
- package/build/treb-export/src/workbook.js.map +1 -0
- package/build/treb-export/src/xml-test.d.ts +9 -0
- package/build/treb-export/src/xml-test.js +52 -0
- package/build/treb-export/src/xml-test.js.map +1 -0
- package/build/treb-export/src/xml-utils.d.ts +76 -0
- package/build/treb-export/src/xml-utils.js +223 -0
- package/build/treb-export/src/xml-utils.js.map +1 -0
- package/build/treb-export/src/zip-wrapper.d.ts +22 -0
- package/build/treb-export/src/zip-wrapper.js +93 -0
- package/build/treb-export/src/zip-wrapper.js.map +1 -0
- package/build/treb-format/src/format.d.ts +130 -0
- package/build/treb-format/src/format.js +805 -0
- package/build/treb-format/src/format.js.map +1 -0
- package/build/treb-format/src/format_cache.d.ts +55 -0
- package/build/treb-format/src/format_cache.js +166 -0
- package/build/treb-format/src/format_cache.js.map +1 -0
- package/build/treb-format/src/format_parser.d.ts +70 -0
- package/build/treb-format/src/format_parser.js +618 -0
- package/build/treb-format/src/format_parser.js.map +1 -0
- package/build/treb-format/src/index.d.ts +4 -0
- package/build/treb-format/src/index.js +25 -0
- package/build/treb-format/src/index.js.map +1 -0
- package/build/treb-format/src/number_format_section.d.ts +58 -0
- package/build/treb-format/src/number_format_section.js +78 -0
- package/build/treb-format/src/number_format_section.js.map +1 -0
- package/build/treb-format/src/value_parser.d.ts +48 -0
- package/build/treb-format/src/value_parser.js +244 -0
- package/build/treb-format/src/value_parser.js.map +1 -0
- package/build/treb-grid/src/editors/autocomplete.d.ts +39 -0
- package/build/treb-grid/src/editors/autocomplete.js +316 -0
- package/build/treb-grid/src/editors/autocomplete.js.map +1 -0
- package/build/treb-grid/src/editors/autocomplete_matcher.d.ts +74 -0
- package/build/treb-grid/src/editors/autocomplete_matcher.js +212 -0
- package/build/treb-grid/src/editors/autocomplete_matcher.js.map +1 -0
- package/build/treb-grid/src/editors/editor.d.ts +214 -0
- package/build/treb-grid/src/editors/editor.js +879 -0
- package/build/treb-grid/src/editors/editor.js.map +1 -0
- package/build/treb-grid/src/editors/external_editor.d.ts +11 -0
- package/build/treb-grid/src/editors/external_editor.js +118 -0
- package/build/treb-grid/src/editors/external_editor.js.map +1 -0
- package/build/treb-grid/src/editors/formula_bar.d.ts +85 -0
- package/build/treb-grid/src/editors/formula_bar.js +444 -0
- package/build/treb-grid/src/editors/formula_bar.js.map +1 -0
- package/build/treb-grid/src/editors/overlay_editor.d.ts +85 -0
- package/build/treb-grid/src/editors/overlay_editor.js +353 -0
- package/build/treb-grid/src/editors/overlay_editor.js.map +1 -0
- package/build/treb-grid/src/index.d.ts +12 -0
- package/build/treb-grid/src/index.js +28 -0
- package/build/treb-grid/src/index.js.map +1 -0
- package/build/treb-grid/src/layout/base_layout.d.ts +346 -0
- package/build/treb-grid/src/layout/base_layout.js +2050 -0
- package/build/treb-grid/src/layout/base_layout.js.map +1 -0
- package/build/treb-grid/src/layout/grid_layout.d.ts +19 -0
- package/build/treb-grid/src/layout/grid_layout.js +235 -0
- package/build/treb-grid/src/layout/grid_layout.js.map +1 -0
- package/build/treb-grid/src/layout/mock-layout.d.ts +10 -0
- package/build/treb-grid/src/layout/mock-layout.js +37 -0
- package/build/treb-grid/src/layout/mock-layout.js.map +1 -0
- package/build/treb-grid/src/render/selection-renderer.d.ts +97 -0
- package/build/treb-grid/src/render/selection-renderer.js +315 -0
- package/build/treb-grid/src/render/selection-renderer.js.map +1 -0
- package/build/treb-grid/src/render/svg_header_overlay.d.ts +20 -0
- package/build/treb-grid/src/render/svg_header_overlay.js +76 -0
- package/build/treb-grid/src/render/svg_header_overlay.js.map +1 -0
- package/build/treb-grid/src/render/svg_selection_block.d.ts +27 -0
- package/build/treb-grid/src/render/svg_selection_block.js +106 -0
- package/build/treb-grid/src/render/svg_selection_block.js.map +1 -0
- package/build/treb-grid/src/render/tile_renderer.d.ts +121 -0
- package/build/treb-grid/src/render/tile_renderer.js +1609 -0
- package/build/treb-grid/src/render/tile_renderer.js.map +1 -0
- package/build/treb-grid/src/types/border_constants.d.ts +9 -0
- package/build/treb-grid/src/types/border_constants.js +34 -0
- package/build/treb-grid/src/types/border_constants.js.map +1 -0
- package/build/treb-grid/src/types/clipboard_data.d.ts +11 -0
- package/build/treb-grid/src/types/clipboard_data.js +22 -0
- package/build/treb-grid/src/types/clipboard_data.js.map +1 -0
- package/build/treb-grid/src/types/clipboard_data2.d.ts +46 -0
- package/build/treb-grid/src/types/clipboard_data2.js +22 -0
- package/build/treb-grid/src/types/clipboard_data2.js.map +1 -0
- package/build/treb-grid/src/types/drag_mask.d.ts +10 -0
- package/build/treb-grid/src/types/drag_mask.js +78 -0
- package/build/treb-grid/src/types/drag_mask.js.map +1 -0
- package/build/treb-grid/src/types/external_editor_config.d.ts +33 -0
- package/build/treb-grid/src/types/external_editor_config.js +22 -0
- package/build/treb-grid/src/types/external_editor_config.js.map +1 -0
- package/build/treb-grid/src/types/grid.d.ts +806 -0
- package/build/treb-grid/src/types/grid.js +6410 -0
- package/build/treb-grid/src/types/grid.js.map +1 -0
- package/build/treb-grid/src/types/grid_base.d.ts +442 -0
- package/build/treb-grid/src/types/grid_base.js +3523 -0
- package/build/treb-grid/src/types/grid_base.js.map +1 -0
- package/build/treb-grid/src/types/grid_command.d.ts +408 -0
- package/build/treb-grid/src/types/grid_command.js +75 -0
- package/build/treb-grid/src/types/grid_command.js.map +1 -0
- package/build/treb-grid/src/types/grid_events.d.ts +93 -0
- package/build/treb-grid/src/types/grid_events.js +36 -0
- package/build/treb-grid/src/types/grid_events.js.map +1 -0
- package/build/treb-grid/src/types/grid_options.d.ts +50 -0
- package/build/treb-grid/src/types/grid_options.js +34 -0
- package/build/treb-grid/src/types/grid_options.js.map +1 -0
- package/build/treb-grid/src/types/scale-control.d.ts +21 -0
- package/build/treb-grid/src/types/scale-control.js +148 -0
- package/build/treb-grid/src/types/scale-control.js.map +1 -0
- package/build/treb-grid/src/types/set_range_options.d.ts +24 -0
- package/build/treb-grid/src/types/set_range_options.js +22 -0
- package/build/treb-grid/src/types/set_range_options.js.map +1 -0
- package/build/treb-grid/src/types/tab_bar.d.ts +84 -0
- package/build/treb-grid/src/types/tab_bar.js +426 -0
- package/build/treb-grid/src/types/tab_bar.js.map +1 -0
- package/build/treb-grid/src/types/tile.d.ts +29 -0
- package/build/treb-grid/src/types/tile.js +22 -0
- package/build/treb-grid/src/types/tile.js.map +1 -0
- package/build/treb-grid/src/types/update_flags.d.ts +48 -0
- package/build/treb-grid/src/types/update_flags.js +22 -0
- package/build/treb-grid/src/types/update_flags.js.map +1 -0
- package/build/treb-grid/src/util/fontmetrics.d.ts +21 -0
- package/build/treb-grid/src/util/fontmetrics.js +82 -0
- package/build/treb-grid/src/util/fontmetrics.js.map +1 -0
- package/build/treb-grid/src/util/ua.d.ts +33 -0
- package/build/treb-grid/src/util/ua.js +86 -0
- package/build/treb-grid/src/util/ua.js.map +1 -0
- package/build/treb-parser/src/csv-parser.d.ts +13 -0
- package/build/treb-parser/src/csv-parser.js +107 -0
- package/build/treb-parser/src/csv-parser.js.map +1 -0
- package/build/treb-parser/src/index.d.ts +4 -0
- package/build/treb-parser/src/index.js +25 -0
- package/build/treb-parser/src/index.js.map +1 -0
- package/build/treb-parser/src/md-parser.d.ts +97 -0
- package/build/treb-parser/src/md-parser.js +403 -0
- package/build/treb-parser/src/md-parser.js.map +1 -0
- package/build/treb-parser/src/parser-types.d.ts +345 -0
- package/build/treb-parser/src/parser-types.js +53 -0
- package/build/treb-parser/src/parser-types.js.map +1 -0
- package/build/treb-parser/src/parser.d.ts +422 -0
- package/build/treb-parser/src/parser.js +2418 -0
- package/build/treb-parser/src/parser.js.map +1 -0
- package/build/treb-utils/src/event_source.d.ts +34 -0
- package/build/treb-utils/src/event_source.js +110 -0
- package/build/treb-utils/src/event_source.js.map +1 -0
- package/build/treb-utils/src/ievent_source.d.ts +9 -0
- package/build/treb-utils/src/ievent_source.js +22 -0
- package/build/treb-utils/src/ievent_source.js.map +1 -0
- package/build/treb-utils/src/index.d.ts +6 -0
- package/build/treb-utils/src/index.js +30 -0
- package/build/treb-utils/src/index.js.map +1 -0
- package/build/treb-utils/src/measurement.d.ts +42 -0
- package/build/treb-utils/src/measurement.js +145 -0
- package/build/treb-utils/src/measurement.js.map +1 -0
- package/build/treb-utils/src/scale.d.ts +16 -0
- package/build/treb-utils/src/scale.js +106 -0
- package/build/treb-utils/src/scale.js.map +1 -0
- package/build/treb-utils/src/serialize_html.d.ts +5 -0
- package/build/treb-utils/src/serialize_html.js +128 -0
- package/build/treb-utils/src/serialize_html.js.map +1 -0
- package/build/treb-utils/src/validate_uri.d.ts +20 -0
- package/build/treb-utils/src/validate_uri.js +55 -0
- package/build/treb-utils/src/validate_uri.js.map +1 -0
- package/dist/{chunk-XD5PEZBZ.mjs → chunk-E35ONJUS.mjs} +1 -1
- package/dist/treb-export-worker.mjs +2 -2
- package/dist/treb-spreadsheet.mjs +4 -4
- package/package.json +1 -1
|
@@ -0,0 +1,2611 @@
|
|
|
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-2026 trebco, llc.
|
|
18
|
+
* info@treb.app
|
|
19
|
+
*
|
|
20
|
+
*/
|
|
21
|
+
import * as Utils from '../utilities';
|
|
22
|
+
// import { StringUnion, NumberUnion } from '../utilities';
|
|
23
|
+
import { ReferenceError, NAError, ArgumentError, DivideByZeroError, ValueError } from '../function-error';
|
|
24
|
+
import { Box, ValueType, GetValueType, ComplexOrReal, IsComplex, Area } from 'treb-base-types';
|
|
25
|
+
import { Sparkline } from './sparkline';
|
|
26
|
+
import { LotusDate, UnlotusDate } from 'treb-format';
|
|
27
|
+
import { ClickCheckbox, RenderCheckbox } from './checkbox';
|
|
28
|
+
import { UnionIsMetadata } from '../expression-calculator';
|
|
29
|
+
import { Exp as ComplexExp, Power as ComplexPower, Multiply as ComplexMultply } from '../complex-math';
|
|
30
|
+
import * as ComplexMath from '../complex-math';
|
|
31
|
+
import { CoerceComplex } from './function-utilities';
|
|
32
|
+
import { ConstructDate } from './date-utils';
|
|
33
|
+
// import type { CalculationContext } from '../descriptors';
|
|
34
|
+
/**
|
|
35
|
+
* BaseFunctionLibrary is a static object that has basic spreadsheet
|
|
36
|
+
* functions and associated metadata (there's also a list of aliases).
|
|
37
|
+
*
|
|
38
|
+
* Calculator should register this one first, followed by any other
|
|
39
|
+
* application-specific libraries.
|
|
40
|
+
*
|
|
41
|
+
* FIXME: there's no reason this has to be a single, monolithic library.
|
|
42
|
+
* we could split up by category or something.
|
|
43
|
+
*
|
|
44
|
+
* ALSO: add category to descriptor.
|
|
45
|
+
*/
|
|
46
|
+
/** milliseconds in one day, used in time functions */
|
|
47
|
+
// const DAY_MS = 1000 * 60 * 60 * 24;
|
|
48
|
+
// some functions have semantics that can't be represented inline,
|
|
49
|
+
// or we may want to refer to them from other functions.
|
|
50
|
+
// OK, just one.
|
|
51
|
+
const edate_calc = (start, months) => {
|
|
52
|
+
let date = new Date(LotusDate(start));
|
|
53
|
+
let month = date.getUTCMonth() + months;
|
|
54
|
+
let year = date.getUTCFullYear();
|
|
55
|
+
// if we don't ensure the time we'll wind up hitting boundary cases
|
|
56
|
+
date.setUTCHours(12);
|
|
57
|
+
date.setUTCMinutes(0);
|
|
58
|
+
date.setUTCSeconds(0);
|
|
59
|
+
date.setUTCMilliseconds(0);
|
|
60
|
+
while (month < 0) {
|
|
61
|
+
month += 12;
|
|
62
|
+
year--;
|
|
63
|
+
}
|
|
64
|
+
while (month > 11) {
|
|
65
|
+
month -= 12;
|
|
66
|
+
year++;
|
|
67
|
+
}
|
|
68
|
+
date.setUTCMonth(month);
|
|
69
|
+
date.setUTCFullYear(year);
|
|
70
|
+
// if this rolls over the month, then we need to go back to the
|
|
71
|
+
// last valid day of the month. so jan 31 + 1 month needs to equal
|
|
72
|
+
// feb 28 (feb 29 in leap year).
|
|
73
|
+
const check_month = date.getUTCMonth();
|
|
74
|
+
if (check_month !== month) {
|
|
75
|
+
const days = date.getUTCDate();
|
|
76
|
+
date = new Date(date.getTime() - (days * 86400 * 1000));
|
|
77
|
+
}
|
|
78
|
+
return date;
|
|
79
|
+
};
|
|
80
|
+
const zlookup_arguments = [
|
|
81
|
+
{
|
|
82
|
+
name: "Lookup value",
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: "Table",
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: "Result index",
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: "Inexact",
|
|
92
|
+
default: true,
|
|
93
|
+
},
|
|
94
|
+
];
|
|
95
|
+
/**
|
|
96
|
+
* unified VLOOKUP/HLOOKUP. ordinarily we'd call it XLOOKUP but that's taken.
|
|
97
|
+
* FIXME: can't use use that function for this?
|
|
98
|
+
*/
|
|
99
|
+
const ZLookup = (value, table, col, inexact = true, transpose = false) => {
|
|
100
|
+
if (transpose) {
|
|
101
|
+
table = Utils.TransposeArray(table);
|
|
102
|
+
}
|
|
103
|
+
col = Math.max(0, col - 1);
|
|
104
|
+
// inexact is the default. this assumes that the data is sorted,
|
|
105
|
+
// either numerically or alphabetically. it returns the closest
|
|
106
|
+
// value without going over -- meaning walk the list, and when
|
|
107
|
+
// you're over return the _previous_ item. except if there's an
|
|
108
|
+
// exact match, I guess, in that case return the exact match.
|
|
109
|
+
// FIXME: there's a hint in the docs for XLOOKUP that this might
|
|
110
|
+
// be using a binary search. not sure why, but that might be
|
|
111
|
+
// correct.
|
|
112
|
+
if (inexact) {
|
|
113
|
+
let result = table[col][0];
|
|
114
|
+
if (typeof value === 'number') {
|
|
115
|
+
let compare = Number(table[0][0]);
|
|
116
|
+
if (isNaN(compare) || compare > value) {
|
|
117
|
+
return NAError();
|
|
118
|
+
}
|
|
119
|
+
for (let i = 1; i < table[0].length; i++) {
|
|
120
|
+
compare = Number(table[0][i]);
|
|
121
|
+
if (isNaN(compare) || compare > value) {
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
result = table[col][i];
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
value = (value || '').toString().toLowerCase(); // ?
|
|
129
|
+
let compare = (table[0][0] || '').toString().toLowerCase();
|
|
130
|
+
if (compare.localeCompare(value) > 0) {
|
|
131
|
+
return NAError();
|
|
132
|
+
}
|
|
133
|
+
for (let i = 1; i < table[0].length; i++) {
|
|
134
|
+
compare = (table[0][i] || '').toString().toLowerCase();
|
|
135
|
+
if (compare.localeCompare(value) > 0) {
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
result = table[col][i];
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return Box(result);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
for (let i = 0; i < table[0].length; i++) {
|
|
145
|
+
if (table[0][i] == value) { // ==
|
|
146
|
+
return Box(table[col][i]);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return NAError();
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
const NumberArgument = (argument, default_value = false) => {
|
|
153
|
+
if (!argument) {
|
|
154
|
+
return default_value;
|
|
155
|
+
}
|
|
156
|
+
switch (argument.type) {
|
|
157
|
+
case ValueType.number:
|
|
158
|
+
return argument.value;
|
|
159
|
+
case ValueType.undefined:
|
|
160
|
+
return default_value;
|
|
161
|
+
}
|
|
162
|
+
return false;
|
|
163
|
+
};
|
|
164
|
+
/**
|
|
165
|
+
* helper for trig functions, curious what this does to size
|
|
166
|
+
*/
|
|
167
|
+
const TrigFunction = (real, complex, description) => {
|
|
168
|
+
return {
|
|
169
|
+
description,
|
|
170
|
+
arguments: [
|
|
171
|
+
{ name: 'number', boxed: true, unroll: true },
|
|
172
|
+
],
|
|
173
|
+
fn: (a) => {
|
|
174
|
+
if (a.type === ValueType.number) {
|
|
175
|
+
return { type: ValueType.number, value: real(a.value) };
|
|
176
|
+
}
|
|
177
|
+
if (a.type === ValueType.complex) {
|
|
178
|
+
return { type: ValueType.complex, value: complex(a.value) };
|
|
179
|
+
}
|
|
180
|
+
return ArgumentError();
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
/**
|
|
185
|
+
* helper for sorting. kind of weird rules
|
|
186
|
+
*/
|
|
187
|
+
const SortHelper = (order, av, bv) => {
|
|
188
|
+
// OK from what I can tell the rules are
|
|
189
|
+
//
|
|
190
|
+
// (1) no type coercion (but see below for blanks)
|
|
191
|
+
// (2) strings are sorted case-insensitive
|
|
192
|
+
// (3) blank values are coerced -> 0 but these are not sorted as numbers
|
|
193
|
+
// (4) order is numbers, strings, booleans, then blanks
|
|
194
|
+
//
|
|
195
|
+
// some of which makes sense, I guess...
|
|
196
|
+
//
|
|
197
|
+
// blanks are always sorted last, irrespective of sort order. which
|
|
198
|
+
// means we can't sort and then reverse, because reverse sort is not
|
|
199
|
+
// the inverse of forward sort
|
|
200
|
+
// special case
|
|
201
|
+
if (av === bv) {
|
|
202
|
+
return 0;
|
|
203
|
+
}
|
|
204
|
+
// special case
|
|
205
|
+
if (av === undefined) {
|
|
206
|
+
return 1;
|
|
207
|
+
}
|
|
208
|
+
if (bv === undefined) {
|
|
209
|
+
return -1;
|
|
210
|
+
}
|
|
211
|
+
// actual comparisons
|
|
212
|
+
if (typeof av === 'number' && typeof bv === 'number') {
|
|
213
|
+
return (av - bv) * order;
|
|
214
|
+
}
|
|
215
|
+
if (typeof av === 'string' && typeof bv === 'string') {
|
|
216
|
+
return av.toLocaleLowerCase().localeCompare(bv.toLowerCase()) * order;
|
|
217
|
+
}
|
|
218
|
+
if (typeof av === 'boolean' && typeof bv === 'boolean') {
|
|
219
|
+
return av ? order : -order;
|
|
220
|
+
}
|
|
221
|
+
if (IsComplex(av) && IsComplex(bv)) {
|
|
222
|
+
return 0; // no sort order
|
|
223
|
+
}
|
|
224
|
+
// type rules
|
|
225
|
+
const types = [av, bv].map(x => {
|
|
226
|
+
switch (typeof x) {
|
|
227
|
+
case 'number':
|
|
228
|
+
return 0;
|
|
229
|
+
case 'string':
|
|
230
|
+
return 2;
|
|
231
|
+
case 'boolean':
|
|
232
|
+
return 3;
|
|
233
|
+
default:
|
|
234
|
+
if (IsComplex(x)) {
|
|
235
|
+
return 1;
|
|
236
|
+
}
|
|
237
|
+
return 4;
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
return (types[0] - types[1]) * order;
|
|
241
|
+
};
|
|
242
|
+
/**
|
|
243
|
+
* alternate functions. these are used (atm) only for changing complex
|
|
244
|
+
* behavior.
|
|
245
|
+
*/
|
|
246
|
+
export const AltFunctionLibrary = {
|
|
247
|
+
Sqrt: {
|
|
248
|
+
description: 'Returns the square root of the argument',
|
|
249
|
+
arguments: [
|
|
250
|
+
{ boxed: true, unroll: true },
|
|
251
|
+
],
|
|
252
|
+
fn: (ref) => {
|
|
253
|
+
if (ref.type === ValueType.complex) {
|
|
254
|
+
const value = ComplexPower(ref.value, { real: 0.5, imaginary: 0 });
|
|
255
|
+
return ComplexOrReal(value);
|
|
256
|
+
}
|
|
257
|
+
else if (ref.type === ValueType.undefined || !ref.value) {
|
|
258
|
+
return {
|
|
259
|
+
type: ValueType.number, value: 0,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
else if (ref.type === ValueType.number && ref.value < 0) {
|
|
263
|
+
const value = ComplexPower({ real: ref.value, imaginary: 0 }, { real: 0.5, imaginary: 0 });
|
|
264
|
+
return {
|
|
265
|
+
type: ValueType.complex,
|
|
266
|
+
value,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
else if (ref.type === ValueType.number) {
|
|
270
|
+
const value = Math.sqrt(ref.value);
|
|
271
|
+
if (isNaN(value)) {
|
|
272
|
+
return ValueError();
|
|
273
|
+
}
|
|
274
|
+
return { type: ValueType.number, value };
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
/*
|
|
278
|
+
const value = Math.sqrt(ref.value);
|
|
279
|
+
if (isNaN(value)) {
|
|
280
|
+
return ValueError();
|
|
281
|
+
}
|
|
282
|
+
return { type: ValueType.number, value };
|
|
283
|
+
*/
|
|
284
|
+
return ValueError();
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
Power: {
|
|
289
|
+
description: 'Returns base raised to the given power',
|
|
290
|
+
arguments: [
|
|
291
|
+
{ name: 'base', boxed: true, unroll: true, },
|
|
292
|
+
{ name: 'exponent', boxed: true, unroll: true, }
|
|
293
|
+
],
|
|
294
|
+
fn: (base, exponent) => {
|
|
295
|
+
// we're leaking complex numbers here because our functions are
|
|
296
|
+
// very slightly imprecise. I would like to stop doing that. try to
|
|
297
|
+
// use real math unless absolutely necessary.
|
|
298
|
+
// in the alternative we could update the epsilon on our ComplexOrReal
|
|
299
|
+
// function, but I would prefer not to do that if we don't have to.
|
|
300
|
+
// so: if both arguments are real, and base is >= 0 we can use real math.
|
|
301
|
+
// also if exponent is either 0 or >= 1 we can use real math.
|
|
302
|
+
if (base.type === ValueType.number && exponent.type === ValueType.number) {
|
|
303
|
+
if (base.value >= 0 || exponent.value === 0 || Math.abs(exponent.value) >= 1) {
|
|
304
|
+
const value = Math.pow(base.value, exponent.value);
|
|
305
|
+
if (isNaN(value)) {
|
|
306
|
+
return ValueError();
|
|
307
|
+
}
|
|
308
|
+
return { type: ValueType.number, value };
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
const a = CoerceComplex(base);
|
|
312
|
+
const b = CoerceComplex(exponent);
|
|
313
|
+
if (a && b) {
|
|
314
|
+
const value = ComplexPower(a, b);
|
|
315
|
+
return ComplexOrReal(value);
|
|
316
|
+
}
|
|
317
|
+
return ValueError();
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
};
|
|
321
|
+
// use a single, static object for base functions
|
|
322
|
+
export const BaseFunctionLibrary = {
|
|
323
|
+
// not sure why there are functions for booleans, but there are
|
|
324
|
+
True: {
|
|
325
|
+
fn: () => ({ type: ValueType.boolean, value: true }),
|
|
326
|
+
},
|
|
327
|
+
False: {
|
|
328
|
+
fn: () => ({ type: ValueType.boolean, value: false }),
|
|
329
|
+
},
|
|
330
|
+
Int: {
|
|
331
|
+
fn: (value) => {
|
|
332
|
+
return { type: ValueType.number, value: Math.floor(value) };
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
Rand: {
|
|
336
|
+
volatile: true,
|
|
337
|
+
fn: () => { return { type: ValueType.number, value: Math.random() }; },
|
|
338
|
+
},
|
|
339
|
+
RandArray: {
|
|
340
|
+
volatile: true,
|
|
341
|
+
arguments: [
|
|
342
|
+
{ name: 'rows' },
|
|
343
|
+
{ name: 'columns' },
|
|
344
|
+
{ name: 'min' },
|
|
345
|
+
{ name: 'max' },
|
|
346
|
+
{ name: 'integer' },
|
|
347
|
+
],
|
|
348
|
+
description: 'Returns an array of uniformly-distributed random numbers',
|
|
349
|
+
fn: (rows = 1, columns = 1, min = 0, max = 1, integer = false) => {
|
|
350
|
+
const value = [];
|
|
351
|
+
if (integer) {
|
|
352
|
+
const range = max - min + 1;
|
|
353
|
+
for (let i = 0; i < columns; i++) {
|
|
354
|
+
const row = [];
|
|
355
|
+
for (let j = 0; j < rows; j++) {
|
|
356
|
+
row.push({
|
|
357
|
+
type: ValueType.number,
|
|
358
|
+
value: Math.floor(Math.random() * range + min),
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
value.push(row);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
const range = max - min;
|
|
366
|
+
for (let i = 0; i < columns; i++) {
|
|
367
|
+
const row = [];
|
|
368
|
+
for (let j = 0; j < rows; j++) {
|
|
369
|
+
row.push({
|
|
370
|
+
type: ValueType.number,
|
|
371
|
+
value: Math.random() * range + min,
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
value.push(row);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return {
|
|
378
|
+
type: ValueType.array,
|
|
379
|
+
value,
|
|
380
|
+
};
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
RandBetween: {
|
|
384
|
+
arguments: [{ name: 'min' }, { name: 'max' }],
|
|
385
|
+
volatile: true,
|
|
386
|
+
fn: (min = 0, max = 1) => {
|
|
387
|
+
if (min > max) {
|
|
388
|
+
const tmp = min;
|
|
389
|
+
min = max;
|
|
390
|
+
max = tmp;
|
|
391
|
+
}
|
|
392
|
+
return { type: ValueType.number, value: Math.floor(Math.random() * (max + 1 - min) + min) };
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
Sum: {
|
|
396
|
+
description: 'Adds arguments and ranges',
|
|
397
|
+
arguments: [{
|
|
398
|
+
boxed: true,
|
|
399
|
+
name: 'values or ranges',
|
|
400
|
+
repeat: true,
|
|
401
|
+
}],
|
|
402
|
+
fn: (...args) => {
|
|
403
|
+
const sum = { real: 0, imaginary: 0 };
|
|
404
|
+
const values = Utils.FlattenBoxed(args); // as UnionValue[];
|
|
405
|
+
for (const value of values) {
|
|
406
|
+
switch (value.type) {
|
|
407
|
+
case ValueType.number:
|
|
408
|
+
sum.real += value.value;
|
|
409
|
+
break;
|
|
410
|
+
case ValueType.boolean:
|
|
411
|
+
// sum.real += (value.value ? 1 : 0); // ??
|
|
412
|
+
break;
|
|
413
|
+
case ValueType.complex:
|
|
414
|
+
sum.real += value.value.real;
|
|
415
|
+
sum.imaginary += value.value.imaginary;
|
|
416
|
+
break;
|
|
417
|
+
case ValueType.error: return value;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return ComplexOrReal(sum);
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
SumSQ: {
|
|
424
|
+
description: 'Returns the sum of the squares of all arguments',
|
|
425
|
+
arguments: [{ boxed: true, name: 'values or ranges', repeat: true, }],
|
|
426
|
+
fn: (...args) => {
|
|
427
|
+
const sum = { real: 0, imaginary: 0 };
|
|
428
|
+
const values = Utils.FlattenBoxed(args); // as UnionValue[];
|
|
429
|
+
for (const value of values) {
|
|
430
|
+
switch (value.type) {
|
|
431
|
+
case ValueType.number:
|
|
432
|
+
sum.real += value.value * value.value;
|
|
433
|
+
break;
|
|
434
|
+
case ValueType.boolean:
|
|
435
|
+
// sum.real += (value.value ? 1 : 0); // ??
|
|
436
|
+
break;
|
|
437
|
+
case ValueType.complex:
|
|
438
|
+
{
|
|
439
|
+
const squared = ComplexMath.Multiply(value.value, value.value);
|
|
440
|
+
// sum.real += value.value.real;
|
|
441
|
+
// sum.imaginary += value.value.imaginary;
|
|
442
|
+
sum.real += squared.real;
|
|
443
|
+
sum.imaginary += squared.imaginary;
|
|
444
|
+
}
|
|
445
|
+
break;
|
|
446
|
+
case ValueType.error: return value;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return ComplexOrReal(sum);
|
|
450
|
+
},
|
|
451
|
+
},
|
|
452
|
+
// --- FIXME: break out date functions? --------------------------------------
|
|
453
|
+
EDate: {
|
|
454
|
+
arguments: [
|
|
455
|
+
{
|
|
456
|
+
name: 'Start date',
|
|
457
|
+
unroll: true,
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
name: 'Months',
|
|
461
|
+
unroll: true,
|
|
462
|
+
},
|
|
463
|
+
],
|
|
464
|
+
fn: (start, months = 0) => {
|
|
465
|
+
if (typeof start !== 'number' || typeof months !== 'number') {
|
|
466
|
+
return ArgumentError();
|
|
467
|
+
}
|
|
468
|
+
const date = edate_calc(start, months);
|
|
469
|
+
return { type: ValueType.number, value: UnlotusDate(date.getTime(), false) };
|
|
470
|
+
}
|
|
471
|
+
},
|
|
472
|
+
EOMonth: {
|
|
473
|
+
arguments: [
|
|
474
|
+
{
|
|
475
|
+
name: 'Start date',
|
|
476
|
+
unroll: true,
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
name: 'Months',
|
|
480
|
+
unroll: true,
|
|
481
|
+
},
|
|
482
|
+
],
|
|
483
|
+
fn: (start, months = 0) => {
|
|
484
|
+
// this is the same as edate, except it advances to the end of the
|
|
485
|
+
// month. so jan 15, 2023 plus one month -> feb 28, 2023 (last day).
|
|
486
|
+
if (typeof start !== 'number' || typeof months !== 'number') {
|
|
487
|
+
return ArgumentError();
|
|
488
|
+
}
|
|
489
|
+
const date = edate_calc(start, months);
|
|
490
|
+
const month = date.getUTCMonth();
|
|
491
|
+
switch (month) {
|
|
492
|
+
case 1: // feb, special
|
|
493
|
+
{
|
|
494
|
+
const year = date.getUTCFullYear();
|
|
495
|
+
// it's a leap year if it is divisible by 4, unless it's also
|
|
496
|
+
// divisible by 100 AND NOT divisible by 400. 1900 is grand-
|
|
497
|
+
// fathered in via an error in Lotus.
|
|
498
|
+
if (year % 4 === 0 && (year === 1900 || (year % 400 === 0) || (year % 100 !== 0))) {
|
|
499
|
+
date.setUTCDate(29);
|
|
500
|
+
}
|
|
501
|
+
else {
|
|
502
|
+
date.setUTCDate(28);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
break;
|
|
506
|
+
case 0: // jan
|
|
507
|
+
case 2:
|
|
508
|
+
case 4:
|
|
509
|
+
case 6: // july
|
|
510
|
+
case 7: // august
|
|
511
|
+
case 9:
|
|
512
|
+
case 11: // dec
|
|
513
|
+
date.setUTCDate(31);
|
|
514
|
+
break;
|
|
515
|
+
default:
|
|
516
|
+
date.setUTCDate(30);
|
|
517
|
+
break;
|
|
518
|
+
}
|
|
519
|
+
return { type: ValueType.number, value: UnlotusDate(date.getTime(), false) };
|
|
520
|
+
}
|
|
521
|
+
},
|
|
522
|
+
Now: {
|
|
523
|
+
description: 'Returns current time',
|
|
524
|
+
volatile: true,
|
|
525
|
+
fn: () => {
|
|
526
|
+
return { type: ValueType.number, value: UnlotusDate(new Date().getTime()) };
|
|
527
|
+
},
|
|
528
|
+
},
|
|
529
|
+
YearFrac: {
|
|
530
|
+
description: 'Returns the fraction of a year between two dates',
|
|
531
|
+
arguments: [
|
|
532
|
+
{ name: 'Start', },
|
|
533
|
+
{ name: 'End', },
|
|
534
|
+
{ name: 'Basis', default: 0 },
|
|
535
|
+
],
|
|
536
|
+
fn: (start, end, basis) => {
|
|
537
|
+
// is this in the spec? should it not be negative here? (...)
|
|
538
|
+
if (end < start) {
|
|
539
|
+
const temp = start;
|
|
540
|
+
start = end;
|
|
541
|
+
end = temp;
|
|
542
|
+
}
|
|
543
|
+
const delta = Math.max(0, end - start);
|
|
544
|
+
let divisor = 360;
|
|
545
|
+
if (basis && basis < 0 || basis > 4) {
|
|
546
|
+
return ArgumentError();
|
|
547
|
+
}
|
|
548
|
+
// console.info({start, end, basis, delta});
|
|
549
|
+
switch (basis) {
|
|
550
|
+
case 1:
|
|
551
|
+
break;
|
|
552
|
+
case 2:
|
|
553
|
+
break;
|
|
554
|
+
case 3:
|
|
555
|
+
divisor = 365;
|
|
556
|
+
break;
|
|
557
|
+
}
|
|
558
|
+
return {
|
|
559
|
+
type: ValueType.number,
|
|
560
|
+
value: delta / divisor,
|
|
561
|
+
};
|
|
562
|
+
},
|
|
563
|
+
},
|
|
564
|
+
Date: {
|
|
565
|
+
description: 'Constructs a date from year/month/day',
|
|
566
|
+
arguments: [
|
|
567
|
+
{ name: 'year', unroll: true },
|
|
568
|
+
{ name: 'month', unroll: true },
|
|
569
|
+
{ name: 'day', unroll: true },
|
|
570
|
+
],
|
|
571
|
+
fn: (year, month, day) => {
|
|
572
|
+
const date = ConstructDate(year, month, day);
|
|
573
|
+
if (date === false) {
|
|
574
|
+
return ArgumentError();
|
|
575
|
+
}
|
|
576
|
+
return { type: ValueType.number, value: date };
|
|
577
|
+
},
|
|
578
|
+
},
|
|
579
|
+
Today: {
|
|
580
|
+
description: 'Returns current day',
|
|
581
|
+
volatile: true,
|
|
582
|
+
fn: () => {
|
|
583
|
+
const date = new Date();
|
|
584
|
+
date.setMilliseconds(0);
|
|
585
|
+
date.setSeconds(0);
|
|
586
|
+
date.setMinutes(0);
|
|
587
|
+
date.setHours(12);
|
|
588
|
+
return { type: ValueType.number, value: UnlotusDate(date.getTime()) };
|
|
589
|
+
},
|
|
590
|
+
},
|
|
591
|
+
// ---------------------------------------------------------------------------
|
|
592
|
+
IfError: {
|
|
593
|
+
description: 'Returns the original value, or the alternate value if the original value contains an error',
|
|
594
|
+
arguments: [{ name: 'original value', allow_error: true, boxed: true }, { name: 'alternate value' }],
|
|
595
|
+
fn: (ref, value_if_error = 0) => {
|
|
596
|
+
if (ref && ref.type === ValueType.error) {
|
|
597
|
+
return { value: value_if_error, type: GetValueType(value_if_error) };
|
|
598
|
+
}
|
|
599
|
+
return ref;
|
|
600
|
+
},
|
|
601
|
+
},
|
|
602
|
+
IsNA: {
|
|
603
|
+
description: 'Checks if another cell contains a #NA error',
|
|
604
|
+
arguments: [{ name: 'reference', allow_error: true, boxed: true }],
|
|
605
|
+
fn: (...args) => {
|
|
606
|
+
const values = Utils.FlattenBoxed(args);
|
|
607
|
+
for (const value of values) {
|
|
608
|
+
if (value.type === ValueType.error) {
|
|
609
|
+
if (value.value === 'N/A') {
|
|
610
|
+
return { type: ValueType.boolean, value: true };
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
return { type: ValueType.boolean, value: false };
|
|
615
|
+
},
|
|
616
|
+
},
|
|
617
|
+
IsErr: {
|
|
618
|
+
description: 'Checks if another cell contains an error',
|
|
619
|
+
arguments: [{ name: 'reference', allow_error: true, boxed: true }],
|
|
620
|
+
fn: (...args) => {
|
|
621
|
+
const values = Utils.FlattenBoxed(args);
|
|
622
|
+
for (const value of values) {
|
|
623
|
+
if (value.type === ValueType.error && value.value !== 'N/A') {
|
|
624
|
+
return { type: ValueType.boolean, value: true };
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
/*
|
|
628
|
+
if (Array.isArray(ref)) {
|
|
629
|
+
const values = Utils.Flatten(ref) as UnionValue[];
|
|
630
|
+
for (const value of values) {
|
|
631
|
+
if (value.type === ValueType.error) {
|
|
632
|
+
return { type: ValueType.boolean, value: true };
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
else if (ref) {
|
|
637
|
+
return { type: ValueType.boolean, value: ref.type === ValueType.error };
|
|
638
|
+
}
|
|
639
|
+
*/
|
|
640
|
+
return { type: ValueType.boolean, value: false };
|
|
641
|
+
},
|
|
642
|
+
},
|
|
643
|
+
IsError: {
|
|
644
|
+
description: 'Checks if another cell contains an error',
|
|
645
|
+
arguments: [{ name: 'reference', allow_error: true, boxed: true }],
|
|
646
|
+
fn: (...args) => {
|
|
647
|
+
const values = Utils.FlattenBoxed(args);
|
|
648
|
+
for (const value of values) {
|
|
649
|
+
if (value.type === ValueType.error) {
|
|
650
|
+
return { type: ValueType.boolean, value: true };
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
/*
|
|
654
|
+
if (Array.isArray(ref)) {
|
|
655
|
+
const values = Utils.Flatten(ref) as UnionValue[];
|
|
656
|
+
for (const value of values) {
|
|
657
|
+
if (value.type === ValueType.error) {
|
|
658
|
+
return { type: ValueType.boolean, value: true };
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
else if (ref) {
|
|
663
|
+
return { type: ValueType.boolean, value: ref.type === ValueType.error };
|
|
664
|
+
}
|
|
665
|
+
*/
|
|
666
|
+
return { type: ValueType.boolean, value: false };
|
|
667
|
+
},
|
|
668
|
+
},
|
|
669
|
+
/*
|
|
670
|
+
Cell: {
|
|
671
|
+
description: 'Returns data about a cell',
|
|
672
|
+
arguments: [
|
|
673
|
+
{ name: 'type', description: 'Type of data to return', unroll: true, },
|
|
674
|
+
{ name: 'reference', description: 'Cell reference', metadata: true, unroll: true, },
|
|
675
|
+
],
|
|
676
|
+
|
|
677
|
+
// there's no concept of "structure volatile", and structure events
|
|
678
|
+
// don't trigger recalc, so this is not helpful -- we may need to
|
|
679
|
+
// think about both of those things
|
|
680
|
+
|
|
681
|
+
// volatile: true,
|
|
682
|
+
|
|
683
|
+
fn: (type: string, reference: UnionValue): UnionValue => {
|
|
684
|
+
|
|
685
|
+
if (!UnionIsMetadata(reference)) {
|
|
686
|
+
return ReferenceError();
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
if (type) {
|
|
690
|
+
switch (type.toString().toLowerCase()) {
|
|
691
|
+
case 'format':
|
|
692
|
+
return reference.value.format ? // || ReferenceError;
|
|
693
|
+
{ type: ValueType.string, value: reference.value.format } : ReferenceError();
|
|
694
|
+
case 'address':
|
|
695
|
+
|
|
696
|
+
// FIXME: this needs to return a fully-qualified address, we'll need access to the model
|
|
697
|
+
|
|
698
|
+
return { type: ValueType.string, value: '[]' + reference.value.address.label.replace(/\$/g, '') };
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
return { type: ValueType.error, value: NotImplError.error };
|
|
703
|
+
|
|
704
|
+
},
|
|
705
|
+
|
|
706
|
+
},
|
|
707
|
+
*/
|
|
708
|
+
Year: {
|
|
709
|
+
description: 'Returns year from date',
|
|
710
|
+
arguments: [{
|
|
711
|
+
name: 'date', unroll: true,
|
|
712
|
+
}],
|
|
713
|
+
fn: (source) => {
|
|
714
|
+
return Box(new Date(LotusDate(source)).getUTCFullYear());
|
|
715
|
+
},
|
|
716
|
+
},
|
|
717
|
+
Month: {
|
|
718
|
+
description: 'Returns month from date',
|
|
719
|
+
arguments: [{
|
|
720
|
+
name: 'date', unroll: true,
|
|
721
|
+
}],
|
|
722
|
+
fn: (source) => {
|
|
723
|
+
return Box(new Date(LotusDate(source)).getUTCMonth() + 1); // 0-based
|
|
724
|
+
},
|
|
725
|
+
},
|
|
726
|
+
Day: {
|
|
727
|
+
description: 'Returns day of month from date',
|
|
728
|
+
arguments: [{
|
|
729
|
+
name: 'date', unroll: true,
|
|
730
|
+
}],
|
|
731
|
+
fn: (source) => {
|
|
732
|
+
return Box(new Date(LotusDate(source)).getUTCDate());
|
|
733
|
+
},
|
|
734
|
+
},
|
|
735
|
+
Radians: {
|
|
736
|
+
description: 'Converts degrees to radians',
|
|
737
|
+
arguments: [{ name: 'Degrees', description: 'Angle in degrees', unroll: true }],
|
|
738
|
+
fn: (degrees) => {
|
|
739
|
+
return Box(degrees * Math.PI / 180);
|
|
740
|
+
},
|
|
741
|
+
},
|
|
742
|
+
Degrees: {
|
|
743
|
+
description: 'Converts radians to degrees',
|
|
744
|
+
arguments: [{ name: 'Radians', description: 'Angle in radians', unroll: true }],
|
|
745
|
+
fn: (radians) => {
|
|
746
|
+
return Box(radians / Math.PI * 180);
|
|
747
|
+
},
|
|
748
|
+
},
|
|
749
|
+
CountA: {
|
|
750
|
+
description: 'Counts cells that are not empty',
|
|
751
|
+
fn: (...args) => {
|
|
752
|
+
return Box(Utils.FlattenCellValues(args).reduce((a, b) => {
|
|
753
|
+
if (typeof b === 'undefined')
|
|
754
|
+
return a;
|
|
755
|
+
return a + 1;
|
|
756
|
+
}, 0));
|
|
757
|
+
},
|
|
758
|
+
},
|
|
759
|
+
Count: {
|
|
760
|
+
description: 'Counts cells that contain numbers',
|
|
761
|
+
fn: (...args) => {
|
|
762
|
+
return Box(Utils.FlattenCellValues(args).reduce((a, b) => {
|
|
763
|
+
if (typeof b === 'number' || IsComplex(b))
|
|
764
|
+
return a + 1;
|
|
765
|
+
return a;
|
|
766
|
+
}, 0));
|
|
767
|
+
},
|
|
768
|
+
},
|
|
769
|
+
Or: {
|
|
770
|
+
fn: (...args) => {
|
|
771
|
+
let result = false;
|
|
772
|
+
args = Utils.FlattenCellValues(args);
|
|
773
|
+
for (const arg of args) {
|
|
774
|
+
result = result || !!arg;
|
|
775
|
+
}
|
|
776
|
+
return Box(result);
|
|
777
|
+
},
|
|
778
|
+
},
|
|
779
|
+
And: {
|
|
780
|
+
fn: (...args) => {
|
|
781
|
+
let result = true;
|
|
782
|
+
args = Utils.FlattenCellValues(args);
|
|
783
|
+
for (const arg of args) {
|
|
784
|
+
result = result && !!arg;
|
|
785
|
+
}
|
|
786
|
+
return Box(result);
|
|
787
|
+
},
|
|
788
|
+
},
|
|
789
|
+
Not: {
|
|
790
|
+
arguments: [{ unroll: true, boxed: true }],
|
|
791
|
+
fn: (arg) => {
|
|
792
|
+
let value = false;
|
|
793
|
+
if (arg) {
|
|
794
|
+
switch (arg.type) {
|
|
795
|
+
case ValueType.undefined:
|
|
796
|
+
value = false;
|
|
797
|
+
break;
|
|
798
|
+
case ValueType.string:
|
|
799
|
+
case ValueType.boolean:
|
|
800
|
+
case ValueType.number:
|
|
801
|
+
value = !!arg.value;
|
|
802
|
+
break;
|
|
803
|
+
case ValueType.error:
|
|
804
|
+
return arg;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
return Box(!value);
|
|
808
|
+
},
|
|
809
|
+
},
|
|
810
|
+
If: {
|
|
811
|
+
arguments: [
|
|
812
|
+
{ name: 'test value', boxed: true },
|
|
813
|
+
{ name: 'value if true', boxed: true, allow_error: true },
|
|
814
|
+
{ name: 'value if false', boxed: true, allow_error: true },
|
|
815
|
+
],
|
|
816
|
+
/**
|
|
817
|
+
* should we really have defaults for the t/f paths? not sure what X does
|
|
818
|
+
* @returns
|
|
819
|
+
*/
|
|
820
|
+
fn: (a, b = { type: ValueType.boolean, value: true }, c = { type: ValueType.boolean, value: false }) => {
|
|
821
|
+
const b_array = b.type === ValueType.array;
|
|
822
|
+
const c_array = c.type === ValueType.array;
|
|
823
|
+
if (a.type === ValueType.array) {
|
|
824
|
+
return {
|
|
825
|
+
type: ValueType.array,
|
|
826
|
+
value: a.value.map((row, x) => row.map((cell, y) => {
|
|
827
|
+
const value = (cell.type === ValueType.string) ?
|
|
828
|
+
(cell.value.toLowerCase() !== 'false' && cell.value.toLowerCase() !== 'f') : !!cell.value;
|
|
829
|
+
return value ? (b_array ? b.value[x][y] : b) : (c_array ? c.value[x][y] : c);
|
|
830
|
+
})),
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
const value = a.type === ValueType.string ? // UnionIs.String(a) ?
|
|
834
|
+
(a.value.toLowerCase() !== 'false' && a.value.toLowerCase() !== 'f') : !!a.value;
|
|
835
|
+
return value ? b : c;
|
|
836
|
+
},
|
|
837
|
+
},
|
|
838
|
+
Fact: {
|
|
839
|
+
description: 'Returns the factorial of a number',
|
|
840
|
+
arguments: [
|
|
841
|
+
{ name: 'number', unroll: true },
|
|
842
|
+
],
|
|
843
|
+
fn: (number) => {
|
|
844
|
+
number = Math.round(number);
|
|
845
|
+
let value = 1;
|
|
846
|
+
while (number > 1) {
|
|
847
|
+
value *= number;
|
|
848
|
+
number--;
|
|
849
|
+
}
|
|
850
|
+
return {
|
|
851
|
+
type: ValueType.number,
|
|
852
|
+
value,
|
|
853
|
+
};
|
|
854
|
+
},
|
|
855
|
+
},
|
|
856
|
+
Factdouble: {
|
|
857
|
+
description: 'Returns the double factorial of a number',
|
|
858
|
+
arguments: [
|
|
859
|
+
{ name: 'number', unroll: true },
|
|
860
|
+
],
|
|
861
|
+
fn: (number) => {
|
|
862
|
+
number = Math.round(number);
|
|
863
|
+
let value = 1;
|
|
864
|
+
while (number > 1) {
|
|
865
|
+
value *= number;
|
|
866
|
+
number -= 2;
|
|
867
|
+
}
|
|
868
|
+
return {
|
|
869
|
+
type: ValueType.number,
|
|
870
|
+
value,
|
|
871
|
+
};
|
|
872
|
+
},
|
|
873
|
+
},
|
|
874
|
+
Power: {
|
|
875
|
+
description: 'Returns base raised to the given power',
|
|
876
|
+
arguments: [
|
|
877
|
+
{ name: 'base', boxed: true, unroll: true, },
|
|
878
|
+
{ name: 'exponent', boxed: true, unroll: true, }
|
|
879
|
+
],
|
|
880
|
+
fn: (base, exponent) => {
|
|
881
|
+
const a = CoerceComplex(base);
|
|
882
|
+
const b = CoerceComplex(exponent);
|
|
883
|
+
if (!a || !b) {
|
|
884
|
+
return ValueError();
|
|
885
|
+
}
|
|
886
|
+
if (base.type === ValueType.complex || exponent.type === ValueType.complex) {
|
|
887
|
+
return ComplexOrReal(ComplexPower(a, b));
|
|
888
|
+
}
|
|
889
|
+
else {
|
|
890
|
+
const value = Math.pow(a.real, b.real);
|
|
891
|
+
if (isNaN(value)) {
|
|
892
|
+
return ValueError();
|
|
893
|
+
}
|
|
894
|
+
return { type: ValueType.number, value };
|
|
895
|
+
// return Box(Math.pow(base.value, exponent.value))
|
|
896
|
+
}
|
|
897
|
+
},
|
|
898
|
+
},
|
|
899
|
+
Mod: {
|
|
900
|
+
arguments: [
|
|
901
|
+
{ unroll: true },
|
|
902
|
+
{ unroll: true },
|
|
903
|
+
],
|
|
904
|
+
fn: (num, divisor) => {
|
|
905
|
+
if (!divisor) {
|
|
906
|
+
return DivideByZeroError();
|
|
907
|
+
}
|
|
908
|
+
return Box(num % divisor);
|
|
909
|
+
},
|
|
910
|
+
},
|
|
911
|
+
Large: {
|
|
912
|
+
description: 'Returns the nth numeric value from the data, in descending order',
|
|
913
|
+
arguments: [
|
|
914
|
+
{
|
|
915
|
+
name: 'values',
|
|
916
|
+
},
|
|
917
|
+
{
|
|
918
|
+
name: 'index', unroll: true,
|
|
919
|
+
}
|
|
920
|
+
],
|
|
921
|
+
fn: (data, index) => {
|
|
922
|
+
if (index <= 0) {
|
|
923
|
+
return ArgumentError();
|
|
924
|
+
}
|
|
925
|
+
// const flat = Utils.FlattenCellValues(data);
|
|
926
|
+
// const numeric: number[] = flat.filter((test): test is number => typeof test === 'number');
|
|
927
|
+
const numeric = Utils.FlattenNumbers(data);
|
|
928
|
+
numeric.sort((a, b) => b - a);
|
|
929
|
+
if (index <= numeric.length) {
|
|
930
|
+
return {
|
|
931
|
+
type: ValueType.number,
|
|
932
|
+
value: numeric[index - 1],
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
return ArgumentError();
|
|
936
|
+
},
|
|
937
|
+
},
|
|
938
|
+
Small: {
|
|
939
|
+
description: 'Returns the nth numeric value from the data, in ascending order',
|
|
940
|
+
arguments: [
|
|
941
|
+
{
|
|
942
|
+
name: 'values',
|
|
943
|
+
},
|
|
944
|
+
{
|
|
945
|
+
name: 'index', unroll: true,
|
|
946
|
+
}
|
|
947
|
+
],
|
|
948
|
+
fn: (data, index) => {
|
|
949
|
+
if (index <= 0) {
|
|
950
|
+
return ArgumentError();
|
|
951
|
+
}
|
|
952
|
+
// const flat = Utils.FlattenCellValues(data);
|
|
953
|
+
// const numeric: number[] = flat.filter((test): test is number => typeof test === 'number');
|
|
954
|
+
const numeric = Utils.FlattenNumbers(data);
|
|
955
|
+
numeric.sort((a, b) => a - b);
|
|
956
|
+
if (index <= numeric.length) {
|
|
957
|
+
return {
|
|
958
|
+
type: ValueType.number,
|
|
959
|
+
value: numeric[index - 1],
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
return ArgumentError();
|
|
963
|
+
},
|
|
964
|
+
},
|
|
965
|
+
/**
|
|
966
|
+
*
|
|
967
|
+
*/
|
|
968
|
+
Filter: {
|
|
969
|
+
description: "Filter an array using a second array.",
|
|
970
|
+
arguments: [
|
|
971
|
+
{ name: 'source', description: 'Source array' },
|
|
972
|
+
{ name: 'filter', description: 'Filter array' },
|
|
973
|
+
// if_empty
|
|
974
|
+
],
|
|
975
|
+
fn: (source, filter) => {
|
|
976
|
+
if (typeof source === 'undefined' || typeof filter === 'undefined') {
|
|
977
|
+
return ArgumentError();
|
|
978
|
+
}
|
|
979
|
+
if (!Array.isArray(source)) {
|
|
980
|
+
source = [[source]];
|
|
981
|
+
}
|
|
982
|
+
if (!Array.isArray(filter)) {
|
|
983
|
+
filter = [[filter]];
|
|
984
|
+
}
|
|
985
|
+
const source_cols = source.length;
|
|
986
|
+
const source_rows = source[0].length;
|
|
987
|
+
const filter_cols = filter.length;
|
|
988
|
+
const filter_rows = filter[0].length;
|
|
989
|
+
// prefer rows
|
|
990
|
+
if (source_rows === filter_rows) {
|
|
991
|
+
const result = [];
|
|
992
|
+
for (let i = 0; i < source_cols; i++) {
|
|
993
|
+
result.push([]);
|
|
994
|
+
}
|
|
995
|
+
for (const [index, entry] of filter[0].entries()) {
|
|
996
|
+
// FIXME: don't allow strings? errors? (...)
|
|
997
|
+
if (entry) {
|
|
998
|
+
for (let i = 0; i < source_cols; i++) {
|
|
999
|
+
result[i].push(Box(source[i][index]));
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
return {
|
|
1004
|
+
type: ValueType.array,
|
|
1005
|
+
value: result,
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
else if (source_cols === filter_cols) {
|
|
1009
|
+
const result = [];
|
|
1010
|
+
for (const [index, [entry]] of filter.entries()) {
|
|
1011
|
+
// FIXME: don't allow strings? errors? (...)
|
|
1012
|
+
if (entry) {
|
|
1013
|
+
result.push(source[index].map(value => Box(value)));
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
return {
|
|
1017
|
+
type: ValueType.array,
|
|
1018
|
+
value: result,
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
return ArgumentError();
|
|
1022
|
+
},
|
|
1023
|
+
},
|
|
1024
|
+
/**
|
|
1025
|
+
* sortby allows multiple sort indexes, but no column sorting
|
|
1026
|
+
*/
|
|
1027
|
+
SortBy: {
|
|
1028
|
+
arguments: [
|
|
1029
|
+
{ name: 'array', },
|
|
1030
|
+
{ name: 'index', },
|
|
1031
|
+
{ name: 'order', description: 'Set to -1 to sort in descending order', default: 1 }
|
|
1032
|
+
],
|
|
1033
|
+
fn: (ref, ...args) => {
|
|
1034
|
+
if (!Array.isArray(ref)) {
|
|
1035
|
+
ref = [[ref]];
|
|
1036
|
+
}
|
|
1037
|
+
// must have at least one sort order?
|
|
1038
|
+
if (args.length < 1) {
|
|
1039
|
+
return ArgumentError();
|
|
1040
|
+
}
|
|
1041
|
+
// ensure any sort argument pairs are valid... I guess they
|
|
1042
|
+
// need to be the same length? what happens if not? [A: error]
|
|
1043
|
+
const rows = ref[0]?.length || 0;
|
|
1044
|
+
const orders = [];
|
|
1045
|
+
const values = [];
|
|
1046
|
+
for (let i = 0; i < args.length; i += 2) {
|
|
1047
|
+
const target = i / 2;
|
|
1048
|
+
let sort_range = args[i];
|
|
1049
|
+
if (!Array.isArray(sort_range)) {
|
|
1050
|
+
sort_range = [[sort_range]];
|
|
1051
|
+
}
|
|
1052
|
+
const check = sort_range[0]?.length || 0;
|
|
1053
|
+
if (check !== rows) {
|
|
1054
|
+
return ArgumentError();
|
|
1055
|
+
}
|
|
1056
|
+
let order = 1;
|
|
1057
|
+
const arg = args[i + 1];
|
|
1058
|
+
if (typeof arg === 'number' && arg < 0) {
|
|
1059
|
+
order = -1;
|
|
1060
|
+
}
|
|
1061
|
+
orders[target] = order;
|
|
1062
|
+
values[target] = sort_range[0]; // (sort_range[0]).slice(0);
|
|
1063
|
+
}
|
|
1064
|
+
const mapped = ref[0]?.map((value, index) => (index));
|
|
1065
|
+
mapped.sort((a, b) => {
|
|
1066
|
+
for (let i = 0; i < orders.length; i++) {
|
|
1067
|
+
const order = orders[i];
|
|
1068
|
+
const value_set = values[i];
|
|
1069
|
+
const result = SortHelper(order, value_set[a], value_set[b]);
|
|
1070
|
+
if (result) {
|
|
1071
|
+
return result;
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
return 0;
|
|
1075
|
+
});
|
|
1076
|
+
// output is same shape
|
|
1077
|
+
const columns = ref.length;
|
|
1078
|
+
const result = [];
|
|
1079
|
+
for (let c = 0; c < columns; c++) {
|
|
1080
|
+
const column = [];
|
|
1081
|
+
for (const index of mapped) {
|
|
1082
|
+
column.push(Box(ref[c][index]));
|
|
1083
|
+
}
|
|
1084
|
+
result.push(column);
|
|
1085
|
+
}
|
|
1086
|
+
return { type: ValueType.array, value: result };
|
|
1087
|
+
},
|
|
1088
|
+
},
|
|
1089
|
+
/**
|
|
1090
|
+
* sort arguments, but ensure we return empty strings to
|
|
1091
|
+
* fill up the result array
|
|
1092
|
+
*
|
|
1093
|
+
* FIXME: instead of boxing all the values, why not pass them in boxed?
|
|
1094
|
+
* was this function just written at the wrong time?
|
|
1095
|
+
*
|
|
1096
|
+
* UPDATE: rewriting to match Excel args
|
|
1097
|
+
*
|
|
1098
|
+
*/
|
|
1099
|
+
Sort: {
|
|
1100
|
+
arguments: [
|
|
1101
|
+
{ name: 'array', },
|
|
1102
|
+
{ name: 'index', },
|
|
1103
|
+
{ name: 'order', description: 'Set to -1 to sort in descending order', default: 1 }
|
|
1104
|
+
],
|
|
1105
|
+
fn: (ref, index = 1, order = 1) => {
|
|
1106
|
+
if (!Array.isArray(ref)) {
|
|
1107
|
+
ref = [[ref]];
|
|
1108
|
+
}
|
|
1109
|
+
// FIXME: transpose for column sort
|
|
1110
|
+
const sort_column = ref[index - 1];
|
|
1111
|
+
if (!sort_column) {
|
|
1112
|
+
return ArgumentError();
|
|
1113
|
+
}
|
|
1114
|
+
// clean (and be lenient)
|
|
1115
|
+
if (order < 0) {
|
|
1116
|
+
order = -1;
|
|
1117
|
+
}
|
|
1118
|
+
else {
|
|
1119
|
+
order = 1;
|
|
1120
|
+
}
|
|
1121
|
+
const mapped = sort_column.map((value, index) => ({ value, index }));
|
|
1122
|
+
mapped.sort((a, b) => SortHelper(order, a.value, b.value));
|
|
1123
|
+
// output is same shape
|
|
1124
|
+
const columns = ref.length;
|
|
1125
|
+
const result = [];
|
|
1126
|
+
for (let c = 0; c < columns; c++) {
|
|
1127
|
+
const column = [];
|
|
1128
|
+
for (const { index } of mapped) {
|
|
1129
|
+
column.push(Box(ref[c][index]));
|
|
1130
|
+
}
|
|
1131
|
+
result.push(column);
|
|
1132
|
+
}
|
|
1133
|
+
return { type: ValueType.array, value: result };
|
|
1134
|
+
},
|
|
1135
|
+
},
|
|
1136
|
+
Transpose: {
|
|
1137
|
+
description: 'Returns transpose of input matrix',
|
|
1138
|
+
arguments: [{ name: 'matrix', boxed: true }],
|
|
1139
|
+
fn: (mat) => {
|
|
1140
|
+
if (mat.type === ValueType.array) {
|
|
1141
|
+
return {
|
|
1142
|
+
type: ValueType.array,
|
|
1143
|
+
value: Utils.Transpose2(mat.value),
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
/*
|
|
1147
|
+
if (Array.isArray(mat)) {
|
|
1148
|
+
return Utils.Transpose2(mat);
|
|
1149
|
+
}
|
|
1150
|
+
*/
|
|
1151
|
+
return mat;
|
|
1152
|
+
}
|
|
1153
|
+
},
|
|
1154
|
+
Max: {
|
|
1155
|
+
fn: (...args) => {
|
|
1156
|
+
return {
|
|
1157
|
+
type: ValueType.number,
|
|
1158
|
+
// value: Math.max.apply(0, Utils.FlattenCellValues(args).filter((x): x is number => typeof x === 'number')),
|
|
1159
|
+
value: Math.max.apply(0, Utils.FlattenNumbers(args)),
|
|
1160
|
+
};
|
|
1161
|
+
},
|
|
1162
|
+
},
|
|
1163
|
+
Min: {
|
|
1164
|
+
fn: (...args) => {
|
|
1165
|
+
return {
|
|
1166
|
+
type: ValueType.number,
|
|
1167
|
+
// value: Math.min.apply(0, Utils.FlattenCellValues(args).filter((x): x is number => typeof x === 'number')),
|
|
1168
|
+
value: Math.min.apply(0, Utils.FlattenNumbers(args)),
|
|
1169
|
+
};
|
|
1170
|
+
},
|
|
1171
|
+
},
|
|
1172
|
+
/*
|
|
1173
|
+
MMult: {
|
|
1174
|
+
description: 'Multiplies two matrices',
|
|
1175
|
+
arguments: [{ name: 'Matrix 1'}, { name: 'Matrix 2'}],
|
|
1176
|
+
fn: (a, b) => {
|
|
1177
|
+
if (!a || !b) return ArgumentError;
|
|
1178
|
+
|
|
1179
|
+
const a_cols = a.length || 0;
|
|
1180
|
+
const a_rows = a[0]?.length || 0;
|
|
1181
|
+
|
|
1182
|
+
const b_cols = b.length || 0;
|
|
1183
|
+
const b_rows = b[0]?.length || 0;
|
|
1184
|
+
|
|
1185
|
+
if (!a_rows || !b_rows || !a_cols || !b_cols
|
|
1186
|
+
|| a_rows !== b_cols || a_cols !== b_rows) return ValueError;
|
|
1187
|
+
|
|
1188
|
+
const result: number[][] = [];
|
|
1189
|
+
|
|
1190
|
+
// slightly confusing because we're column-major
|
|
1191
|
+
|
|
1192
|
+
for (let c = 0; c < b_cols; c++) {
|
|
1193
|
+
result[c] = [];
|
|
1194
|
+
for (let r = 0; r < a_rows; r++) {
|
|
1195
|
+
result[c][r] = 0;
|
|
1196
|
+
for (let x = 0; x < a_cols; x++) {
|
|
1197
|
+
result[c][r] += a[x][r] * b[c][x];
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
return result;
|
|
1202
|
+
|
|
1203
|
+
}
|
|
1204
|
+
},
|
|
1205
|
+
*/
|
|
1206
|
+
SumProduct: {
|
|
1207
|
+
description: 'Returns the sum of pairwise products of two or more ranges',
|
|
1208
|
+
fn: (...args) => {
|
|
1209
|
+
const flattened = args.map(arg => Utils.FlattenCellValues(arg));
|
|
1210
|
+
const len = Math.max.apply(0, flattened.map(x => x.length));
|
|
1211
|
+
let sum = 0;
|
|
1212
|
+
for (let i = 0; i < len; i++) {
|
|
1213
|
+
sum += flattened.reduce((a, arg) => {
|
|
1214
|
+
let ai = arg[i];
|
|
1215
|
+
if (ai === true) {
|
|
1216
|
+
ai = 1;
|
|
1217
|
+
}
|
|
1218
|
+
return (typeof ai === 'number') ? a * ai : 0;
|
|
1219
|
+
}, 1);
|
|
1220
|
+
}
|
|
1221
|
+
return { type: ValueType.number, value: sum };
|
|
1222
|
+
},
|
|
1223
|
+
},
|
|
1224
|
+
/**
|
|
1225
|
+
*
|
|
1226
|
+
* match type:
|
|
1227
|
+
*
|
|
1228
|
+
* 1: largest value <= target value; assumes table is in ascending order.
|
|
1229
|
+
* 0: exact match only.
|
|
1230
|
+
* -1: smallest value >= target value; assumes table is in descending order.
|
|
1231
|
+
*
|
|
1232
|
+
* NOTE that string matches can accept wildcards in Excel, not sure if we
|
|
1233
|
+
* necessarily want to support that... how does string matching deal with
|
|
1234
|
+
* inequalities?
|
|
1235
|
+
* /
|
|
1236
|
+
Match: {
|
|
1237
|
+
fn: (value: CellValue, table: CellValue[][], match_type: 1|0|-1 = 1) => {
|
|
1238
|
+
|
|
1239
|
+
const flat = table.reduce((a, row) => ([...a, ...row]), []);
|
|
1240
|
+
for (let i = 0; i < flat.length; i++) {
|
|
1241
|
+
|
|
1242
|
+
const compare = flat[i];
|
|
1243
|
+
|
|
1244
|
+
console.info("CV", compare, value);
|
|
1245
|
+
|
|
1246
|
+
// this is true regardless of match type... right?
|
|
1247
|
+
if (compare === value) {
|
|
1248
|
+
return { type: ValueType.number, value: i + 1 };
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
if ((typeof compare !== 'undefined' && typeof value !== 'undefined') && (
|
|
1252
|
+
(match_type === 1 && compare > value) ||
|
|
1253
|
+
(match_type === -1 && compare < value))) {
|
|
1254
|
+
|
|
1255
|
+
if (i === 0 || i === flat.length - 1) {
|
|
1256
|
+
return NAError();
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
return { type: ValueType.number, value: i }; // implicit -1
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
}
|
|
1263
|
+
return NAError();
|
|
1264
|
+
},
|
|
1265
|
+
},
|
|
1266
|
+
*/
|
|
1267
|
+
Row: {
|
|
1268
|
+
arguments: [{ name: 'reference', metadata: true }],
|
|
1269
|
+
fn: function (ref) {
|
|
1270
|
+
if (!ref) {
|
|
1271
|
+
if (this?.area) {
|
|
1272
|
+
const value = [];
|
|
1273
|
+
for (let c = this.area.start.column; c <= this.area.end.column; c++) {
|
|
1274
|
+
const col = [];
|
|
1275
|
+
for (let r = this.area.start.row; r <= this.area.end.row; r++) {
|
|
1276
|
+
col.push({
|
|
1277
|
+
type: ValueType.number,
|
|
1278
|
+
value: r + 1,
|
|
1279
|
+
});
|
|
1280
|
+
}
|
|
1281
|
+
value.push(col);
|
|
1282
|
+
}
|
|
1283
|
+
return {
|
|
1284
|
+
type: ValueType.array, value,
|
|
1285
|
+
};
|
|
1286
|
+
}
|
|
1287
|
+
else {
|
|
1288
|
+
return {
|
|
1289
|
+
type: ValueType.number,
|
|
1290
|
+
value: this ? this.address.row + 1 : -1,
|
|
1291
|
+
};
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
if (ref.type === ValueType.array) {
|
|
1295
|
+
const arr = ref.value;
|
|
1296
|
+
const first = arr[0][0];
|
|
1297
|
+
if (UnionIsMetadata(first)) {
|
|
1298
|
+
return {
|
|
1299
|
+
type: ValueType.array,
|
|
1300
|
+
value: [arr[0].map((row, index) => ({
|
|
1301
|
+
type: ValueType.number,
|
|
1302
|
+
value: index + first.value.address.row + 1
|
|
1303
|
+
}))],
|
|
1304
|
+
};
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
else if (UnionIsMetadata(ref)) {
|
|
1308
|
+
return {
|
|
1309
|
+
type: ValueType.number, value: ref.value.address.row + 1,
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
return ArgumentError();
|
|
1313
|
+
},
|
|
1314
|
+
},
|
|
1315
|
+
Column: {
|
|
1316
|
+
arguments: [{ name: 'reference', metadata: true }],
|
|
1317
|
+
fn: function (ref) {
|
|
1318
|
+
if (!ref) {
|
|
1319
|
+
if (this?.area) {
|
|
1320
|
+
const value = [];
|
|
1321
|
+
for (let c = this.area.start.column; c <= this.area.end.column; c++) {
|
|
1322
|
+
const col = [];
|
|
1323
|
+
for (let r = this.area.start.row; r <= this.area.end.row; r++) {
|
|
1324
|
+
col.push({
|
|
1325
|
+
type: ValueType.number,
|
|
1326
|
+
value: c + 1,
|
|
1327
|
+
});
|
|
1328
|
+
}
|
|
1329
|
+
value.push(col);
|
|
1330
|
+
}
|
|
1331
|
+
return {
|
|
1332
|
+
type: ValueType.array, value,
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1335
|
+
else {
|
|
1336
|
+
return {
|
|
1337
|
+
type: ValueType.number,
|
|
1338
|
+
value: this ? this.address.column + 1 : -1,
|
|
1339
|
+
};
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
if (ref.type === ValueType.array) {
|
|
1343
|
+
const arr = ref.value;
|
|
1344
|
+
const first = arr[0][0];
|
|
1345
|
+
if (UnionIsMetadata(first)) {
|
|
1346
|
+
return {
|
|
1347
|
+
type: ValueType.array,
|
|
1348
|
+
value: arr.map((row, index) => [{
|
|
1349
|
+
type: ValueType.number,
|
|
1350
|
+
value: index + first.value.address.column + 1
|
|
1351
|
+
}]),
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
else if (UnionIsMetadata(ref)) {
|
|
1356
|
+
return {
|
|
1357
|
+
type: ValueType.number, value: ref.value.address.row + 1,
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
return ArgumentError();
|
|
1361
|
+
},
|
|
1362
|
+
},
|
|
1363
|
+
Choose: {
|
|
1364
|
+
arguments: [
|
|
1365
|
+
{ name: 'Selected index', },
|
|
1366
|
+
{ name: 'Choice 1...', metadata: true },
|
|
1367
|
+
],
|
|
1368
|
+
return_type: 'reference',
|
|
1369
|
+
description: 'Returns one of a list of choices',
|
|
1370
|
+
fn: (selected, ...choices) => {
|
|
1371
|
+
if (selected < 1 || selected > choices.length) {
|
|
1372
|
+
return ValueError();
|
|
1373
|
+
}
|
|
1374
|
+
const value = choices[selected - 1];
|
|
1375
|
+
// this should be metadata. is there a different object we
|
|
1376
|
+
// might run into? maybe we should refactor how metadata works
|
|
1377
|
+
if (UnionIsMetadata(value)) {
|
|
1378
|
+
return {
|
|
1379
|
+
type: ValueType.object,
|
|
1380
|
+
value: value.value.address,
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
1383
|
+
// check if array is metadata. if it's a literal array
|
|
1384
|
+
// we just want to return it.
|
|
1385
|
+
if (value.type === ValueType.array) {
|
|
1386
|
+
const arr = value.value;
|
|
1387
|
+
const rows = arr.length;
|
|
1388
|
+
const cols = arr[0].length;
|
|
1389
|
+
const first = arr[0][0];
|
|
1390
|
+
const last = arr[rows - 1][cols - 1];
|
|
1391
|
+
if (rows === 1 && cols === 1) {
|
|
1392
|
+
if (UnionIsMetadata(first)) {
|
|
1393
|
+
return {
|
|
1394
|
+
type: ValueType.object,
|
|
1395
|
+
value: first.value.address,
|
|
1396
|
+
};
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
else {
|
|
1400
|
+
if (UnionIsMetadata(first) && UnionIsMetadata(last)) {
|
|
1401
|
+
return {
|
|
1402
|
+
type: ValueType.object,
|
|
1403
|
+
value: {
|
|
1404
|
+
type: 'range',
|
|
1405
|
+
position: 0, id: 0, label: '',
|
|
1406
|
+
start: first.value.address,
|
|
1407
|
+
end: last.value.address,
|
|
1408
|
+
}
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
return {
|
|
1414
|
+
...value, // should we deep-copy in case of an array?
|
|
1415
|
+
};
|
|
1416
|
+
},
|
|
1417
|
+
},
|
|
1418
|
+
/*
|
|
1419
|
+
* rewrite of xlookup to return a reference. better compatibility.
|
|
1420
|
+
* ---
|
|
1421
|
+
*
|
|
1422
|
+
* unsaid anywhere (that I can locate) aboud XLOOKUP is that lookup
|
|
1423
|
+
* array must be one-dimensional. it can be either a row or a column,
|
|
1424
|
+
* but one dimension must be one. that simplifies things quite a bit.
|
|
1425
|
+
*
|
|
1426
|
+
* there's a note in the docs about binary search over the data --
|
|
1427
|
+
* that might explain how inexact VLOOKUP works as well. seems an odd
|
|
1428
|
+
* choice but maybe back in the day it made sense
|
|
1429
|
+
*/
|
|
1430
|
+
XLOOKUP: {
|
|
1431
|
+
arguments: [
|
|
1432
|
+
{ name: 'Lookup value', },
|
|
1433
|
+
{ name: 'Lookup array', },
|
|
1434
|
+
{ name: 'Return array', metadata: true, },
|
|
1435
|
+
{ name: 'Not found', boxed: true },
|
|
1436
|
+
{ name: 'Match mode', default: 0, },
|
|
1437
|
+
{ name: 'Search mode', default: 1, },
|
|
1438
|
+
],
|
|
1439
|
+
return_type: 'reference',
|
|
1440
|
+
xlfn: true,
|
|
1441
|
+
fn: (lookup_value, lookup_array, return_array, not_found, match_mode = 0, search_mode = 1) => {
|
|
1442
|
+
////////
|
|
1443
|
+
if (!return_array) {
|
|
1444
|
+
return ArgumentError();
|
|
1445
|
+
}
|
|
1446
|
+
// const parse_result = this.parser.Parse(reference);
|
|
1447
|
+
// if (parse_result.error || !parse_result.expression) {
|
|
1448
|
+
// return ReferenceError;
|
|
1449
|
+
//}
|
|
1450
|
+
let rng;
|
|
1451
|
+
let return_from_array = false;
|
|
1452
|
+
if (return_array.type === ValueType.array) {
|
|
1453
|
+
// console.info({return_array});
|
|
1454
|
+
const arr = return_array.value;
|
|
1455
|
+
const r = arr.length;
|
|
1456
|
+
const c = arr[0].length;
|
|
1457
|
+
const start = arr[0][0];
|
|
1458
|
+
const end = arr[r - 1][c - 1];
|
|
1459
|
+
if (UnionIsMetadata(start) && UnionIsMetadata(end)) {
|
|
1460
|
+
rng = new Area(start.value.address, end.value.address);
|
|
1461
|
+
}
|
|
1462
|
+
// we can allow a regular array here... perhaps we should
|
|
1463
|
+
// check the dimensions to ensure they match? TODO/FIXME
|
|
1464
|
+
else {
|
|
1465
|
+
return_from_array = true;
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
if (!rng && !return_from_array) {
|
|
1469
|
+
console.info('invalid range');
|
|
1470
|
+
return ReferenceError();
|
|
1471
|
+
}
|
|
1472
|
+
// console.info({rng});
|
|
1473
|
+
/*
|
|
1474
|
+
if (return_array.type === ValueType.array) {
|
|
1475
|
+
|
|
1476
|
+
// subset array. this is constructed, so we can take ownership
|
|
1477
|
+
// and modify it, although it would be safer to copy. also, what's
|
|
1478
|
+
// the cost of functional vs imperative loops these days?
|
|
1479
|
+
|
|
1480
|
+
const end_row = typeof height === 'number' ? (rows + height) : undefined;
|
|
1481
|
+
const end_column = typeof width === 'number' ? (columns + width) : undefined;
|
|
1482
|
+
|
|
1483
|
+
const result: UnionValue = {
|
|
1484
|
+
type: ValueType.array,
|
|
1485
|
+
value: reference.value.slice(rows, end_row).map(row => row.slice(columns, end_column)),
|
|
1486
|
+
};
|
|
1487
|
+
|
|
1488
|
+
return result;
|
|
1489
|
+
|
|
1490
|
+
}
|
|
1491
|
+
*/
|
|
1492
|
+
// FIXME: we could I suppose be more graceful about single values
|
|
1493
|
+
// if passed instead of arrays
|
|
1494
|
+
if (!Array.isArray(lookup_array)) {
|
|
1495
|
+
console.info("lookup is not an array");
|
|
1496
|
+
return ValueError();
|
|
1497
|
+
}
|
|
1498
|
+
const first = lookup_array[0];
|
|
1499
|
+
if (!Array.isArray(first)) {
|
|
1500
|
+
console.info("lookup is not a 2d array");
|
|
1501
|
+
return ValueError();
|
|
1502
|
+
}
|
|
1503
|
+
if (lookup_array.length !== 1 && first.length !== 1) {
|
|
1504
|
+
console.info("lookup array has invalid dimensions");
|
|
1505
|
+
return ValueError();
|
|
1506
|
+
}
|
|
1507
|
+
// FIXME: is it required that the return array be (at least) the
|
|
1508
|
+
// same size? we can return undefineds, but maybe we should error
|
|
1509
|
+
/*
|
|
1510
|
+
if (!Array.isArray(return_array)) {
|
|
1511
|
+
console.info("return array is not an array");
|
|
1512
|
+
return ValueError();
|
|
1513
|
+
}
|
|
1514
|
+
*/
|
|
1515
|
+
const transpose = (lookup_array.length === 1);
|
|
1516
|
+
if (transpose) {
|
|
1517
|
+
lookup_array = Utils.TransposeArray(lookup_array);
|
|
1518
|
+
// return_array = Utils.TransposeArray(return_array);
|
|
1519
|
+
}
|
|
1520
|
+
// maybe reverse...
|
|
1521
|
+
if (search_mode < 0) {
|
|
1522
|
+
lookup_array.reverse();
|
|
1523
|
+
// return_array.reverse();
|
|
1524
|
+
}
|
|
1525
|
+
//
|
|
1526
|
+
// return value at index, transpose if necessary, and return
|
|
1527
|
+
// an array. we might prefer to return a scalar if there's only
|
|
1528
|
+
// one value, not sure what's the intended behavior
|
|
1529
|
+
//
|
|
1530
|
+
const ReturnIndex = (index) => {
|
|
1531
|
+
// FIXME: we could almost certainly merge these two paths
|
|
1532
|
+
if (return_from_array && return_array.type === ValueType.array) {
|
|
1533
|
+
// instead of a range, we're returning values from a static array
|
|
1534
|
+
const src_columns = return_array.value.length;
|
|
1535
|
+
const src_rows = return_array.value[0]?.length || 0;
|
|
1536
|
+
const result = [];
|
|
1537
|
+
let start_row = 0;
|
|
1538
|
+
let end_row = src_rows - 1;
|
|
1539
|
+
let start_column = 0;
|
|
1540
|
+
let end_column = src_columns - 1;
|
|
1541
|
+
if (transpose) {
|
|
1542
|
+
if (search_mode < 0) {
|
|
1543
|
+
index = src_rows - 1 - index; // invert FIXME: test
|
|
1544
|
+
}
|
|
1545
|
+
start_row = end_row = index;
|
|
1546
|
+
}
|
|
1547
|
+
else {
|
|
1548
|
+
if (search_mode < 0) {
|
|
1549
|
+
index = src_columns - 1 - index; // invert FIXME: test
|
|
1550
|
+
}
|
|
1551
|
+
start_column = end_column = index;
|
|
1552
|
+
}
|
|
1553
|
+
for (let c = start_column; c <= end_column; c++) {
|
|
1554
|
+
const column = [];
|
|
1555
|
+
for (let r = start_row; r <= end_row; r++) {
|
|
1556
|
+
column.push(return_array.value[c][r]);
|
|
1557
|
+
}
|
|
1558
|
+
result.push(column);
|
|
1559
|
+
}
|
|
1560
|
+
return {
|
|
1561
|
+
type: ValueType.array,
|
|
1562
|
+
value: result,
|
|
1563
|
+
};
|
|
1564
|
+
}
|
|
1565
|
+
if (!rng) {
|
|
1566
|
+
throw new Error('invalid range');
|
|
1567
|
+
}
|
|
1568
|
+
// console.info("transpose?", transpose, {rng}, 'shape', rng.rows, rng.columns);
|
|
1569
|
+
let start;
|
|
1570
|
+
let end;
|
|
1571
|
+
// I guess "transpose" in this context means "return a row from column(s)"? rename
|
|
1572
|
+
if (transpose) {
|
|
1573
|
+
if (search_mode < 0) {
|
|
1574
|
+
index = rng.rows - 1 - index; // invert FIXME: test
|
|
1575
|
+
}
|
|
1576
|
+
if (index >= 0 && index < rng.rows) {
|
|
1577
|
+
start = {
|
|
1578
|
+
type: 'address',
|
|
1579
|
+
position: 0, id: 1, label: '',
|
|
1580
|
+
row: rng.start.row + index,
|
|
1581
|
+
column: rng.start.column,
|
|
1582
|
+
sheet_id: rng.start.sheet_id,
|
|
1583
|
+
};
|
|
1584
|
+
end = {
|
|
1585
|
+
type: 'address',
|
|
1586
|
+
position: 0, id: 2, label: '',
|
|
1587
|
+
row: rng.start.row + index,
|
|
1588
|
+
column: rng.end.column,
|
|
1589
|
+
sheet_id: rng.start.sheet_id,
|
|
1590
|
+
};
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
else {
|
|
1594
|
+
if (search_mode < 0) {
|
|
1595
|
+
index = rng.columns - 1 - index; // invert FIXME: test
|
|
1596
|
+
}
|
|
1597
|
+
if (index >= 0 && index < rng.columns) {
|
|
1598
|
+
start = {
|
|
1599
|
+
type: 'address',
|
|
1600
|
+
position: 0, id: 1, label: '',
|
|
1601
|
+
row: rng.start.row,
|
|
1602
|
+
column: rng.start.column + index,
|
|
1603
|
+
sheet_id: rng.start.sheet_id,
|
|
1604
|
+
};
|
|
1605
|
+
end = {
|
|
1606
|
+
type: 'address',
|
|
1607
|
+
position: 0, id: 2, label: '',
|
|
1608
|
+
row: rng.end.row,
|
|
1609
|
+
column: rng.start.column + index,
|
|
1610
|
+
sheet_id: rng.start.sheet_id,
|
|
1611
|
+
};
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
if (start && end) {
|
|
1615
|
+
const expr = {
|
|
1616
|
+
type: 'range',
|
|
1617
|
+
position: 0,
|
|
1618
|
+
id: 0,
|
|
1619
|
+
label: '',
|
|
1620
|
+
start, end,
|
|
1621
|
+
};
|
|
1622
|
+
// console.info({expr});
|
|
1623
|
+
return {
|
|
1624
|
+
type: ValueType.object,
|
|
1625
|
+
value: expr,
|
|
1626
|
+
};
|
|
1627
|
+
}
|
|
1628
|
+
return { type: ValueType.undefined };
|
|
1629
|
+
};
|
|
1630
|
+
// if value is not a string, then we can ignore wildcards.
|
|
1631
|
+
// in that case convert to exact match.
|
|
1632
|
+
if (match_mode === 2 && typeof lookup_value !== 'string') {
|
|
1633
|
+
match_mode = 0;
|
|
1634
|
+
}
|
|
1635
|
+
// what does inexact matching mean in this case if the lookup
|
|
1636
|
+
// value is a string or boolean? (...)
|
|
1637
|
+
if ((match_mode === 1 || match_mode === -1) && typeof lookup_value === 'number') {
|
|
1638
|
+
let min_delta = 0;
|
|
1639
|
+
let index = -1;
|
|
1640
|
+
for (let i = 0; i < lookup_array.length; i++) {
|
|
1641
|
+
const value = lookup_array[i][0];
|
|
1642
|
+
if (typeof value === 'number') {
|
|
1643
|
+
// check for exact match first, just in case
|
|
1644
|
+
if (value === lookup_value) {
|
|
1645
|
+
return ReturnIndex(i);
|
|
1646
|
+
}
|
|
1647
|
+
const delta = Math.abs(value - lookup_value);
|
|
1648
|
+
if ((match_mode === 1 && value > lookup_value) || (match_mode === -1 && value < lookup_value)) {
|
|
1649
|
+
if (index < 0 || delta < min_delta) {
|
|
1650
|
+
min_delta = delta;
|
|
1651
|
+
index = i;
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
if (index >= 0) {
|
|
1657
|
+
return ReturnIndex(index);
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
switch (match_mode) {
|
|
1661
|
+
case 2:
|
|
1662
|
+
{
|
|
1663
|
+
// wildcard string match. we only handle strings for
|
|
1664
|
+
// this case (see above).
|
|
1665
|
+
const pattern = Utils.ParseWildcards(lookup_value?.toString() || '');
|
|
1666
|
+
const regex = new RegExp('^' + pattern + '$', 'i'); //.exec(lookup_value);
|
|
1667
|
+
for (let i = 0; i < lookup_array.length; i++) {
|
|
1668
|
+
const value = lookup_array[i][0];
|
|
1669
|
+
if (typeof value === 'string' && regex.exec(value)) {
|
|
1670
|
+
return ReturnIndex(i);
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
break;
|
|
1675
|
+
case 0:
|
|
1676
|
+
if (typeof lookup_value === 'string') {
|
|
1677
|
+
lookup_value = lookup_value.toLowerCase();
|
|
1678
|
+
}
|
|
1679
|
+
for (let i = 0; i < lookup_array.length; i++) {
|
|
1680
|
+
let value = lookup_array[i][0];
|
|
1681
|
+
if (typeof value === 'string') {
|
|
1682
|
+
value = value.toLowerCase();
|
|
1683
|
+
}
|
|
1684
|
+
if (value === lookup_value) {
|
|
1685
|
+
return ReturnIndex(i);
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
break;
|
|
1689
|
+
}
|
|
1690
|
+
// FIXME: if we're expecting to return an array maybe we should
|
|
1691
|
+
// pack it up as an array? if it's not already an array? (...)
|
|
1692
|
+
return (not_found && not_found.type !== ValueType.undefined) ? not_found : NAError();
|
|
1693
|
+
},
|
|
1694
|
+
},
|
|
1695
|
+
/*
|
|
1696
|
+
* unsaid anywhere (that I can locate) aboud XLOOKUP is that lookup
|
|
1697
|
+
* array must be one-dimensional. it can be either a row or a column,
|
|
1698
|
+
* but one dimension must be one. that simplifies things quite a bit.
|
|
1699
|
+
*
|
|
1700
|
+
* there's a note in the docs about binary search over the data --
|
|
1701
|
+
* that might explain how inexact VLOOKUP works as well. seems an odd
|
|
1702
|
+
* choice but maybe back in the day it made sense
|
|
1703
|
+
* /
|
|
1704
|
+
XLOOKUP: {
|
|
1705
|
+
arguments: [
|
|
1706
|
+
{ name: 'Lookup value', },
|
|
1707
|
+
{ name: 'Lookup array', },
|
|
1708
|
+
{ name: 'Return array', address: true, },
|
|
1709
|
+
{ name: 'Not found', boxed: true },
|
|
1710
|
+
{ name: 'Match mode', default: 0, },
|
|
1711
|
+
{ name: 'Search mode', default: 1, },
|
|
1712
|
+
],
|
|
1713
|
+
return_type: 'reference',
|
|
1714
|
+
xlfn: true,
|
|
1715
|
+
fn: (
|
|
1716
|
+
lookup_value: IntrinsicValue,
|
|
1717
|
+
lookup_array: IntrinsicValue[][],
|
|
1718
|
+
return_array: string,
|
|
1719
|
+
not_found?: UnionValue,
|
|
1720
|
+
match_mode = 0,
|
|
1721
|
+
search_mode = 1,
|
|
1722
|
+
): UnionValue => {
|
|
1723
|
+
|
|
1724
|
+
console.info({return_array});
|
|
1725
|
+
|
|
1726
|
+
|
|
1727
|
+
// FIXME: we could I suppose be more graceful about single values
|
|
1728
|
+
// if passed instead of arrays
|
|
1729
|
+
|
|
1730
|
+
if (!Array.isArray(lookup_array)) {
|
|
1731
|
+
console.info("lookup is not an array");
|
|
1732
|
+
return ValueError();
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
const first = lookup_array[0];
|
|
1736
|
+
if (!Array.isArray(first)) {
|
|
1737
|
+
console.info("lookip is not a 2d array");
|
|
1738
|
+
return ValueError();
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
if (lookup_array.length !== 1 && first.length !== 1) {
|
|
1742
|
+
console.info("lookup array has invalid dimensions");
|
|
1743
|
+
return ValueError();
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
// FIXME: is it required that the return array be (at least) the
|
|
1747
|
+
// same size? we can return undefineds, but maybe we should error
|
|
1748
|
+
|
|
1749
|
+
if (!Array.isArray(return_array)) {
|
|
1750
|
+
console.info("return array is not an array");
|
|
1751
|
+
return ValueError();
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
const transpose = (lookup_array.length === 1);
|
|
1755
|
+
if (transpose) {
|
|
1756
|
+
lookup_array = Utils.TransposeArray(lookup_array);
|
|
1757
|
+
return_array = Utils.TransposeArray(return_array);
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
// maybe reverse...
|
|
1761
|
+
|
|
1762
|
+
if (search_mode < 0) {
|
|
1763
|
+
lookup_array.reverse();
|
|
1764
|
+
return_array.reverse();
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
//
|
|
1768
|
+
// return value at index, transpose if necessary, and return
|
|
1769
|
+
// an array. we might prefer to return a scalar if there's only
|
|
1770
|
+
// one value, not sure what's the intended behavior
|
|
1771
|
+
//
|
|
1772
|
+
const ReturnIndex = (index: number): UnionValue => {
|
|
1773
|
+
|
|
1774
|
+
const values = return_array[index];
|
|
1775
|
+
|
|
1776
|
+
if (!values) {
|
|
1777
|
+
return { type: ValueType.undefined };
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
if (!Array.isArray(values)) {
|
|
1781
|
+
return Box(values);
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
let boxes = [values.map(value => Box(value))];
|
|
1785
|
+
|
|
1786
|
+
if (transpose) {
|
|
1787
|
+
boxes = Utils.TransposeArray(boxes);
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
return {
|
|
1791
|
+
type: ValueType.array,
|
|
1792
|
+
value: boxes,
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
};
|
|
1796
|
+
|
|
1797
|
+
// if value is not a string, then we can ignore wildcards.
|
|
1798
|
+
// in that case convert to exact match.
|
|
1799
|
+
|
|
1800
|
+
if (match_mode === 2 && typeof lookup_value !== 'string') {
|
|
1801
|
+
match_mode = 0;
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
// what does inexact matching mean in this case if the lookup
|
|
1805
|
+
// value is a string or boolean? (...)
|
|
1806
|
+
|
|
1807
|
+
if ((match_mode === 1 || match_mode === -1) && typeof lookup_value === 'number') {
|
|
1808
|
+
|
|
1809
|
+
let min_delta = 0;
|
|
1810
|
+
let index = -1;
|
|
1811
|
+
|
|
1812
|
+
for (let i = 0; i < lookup_array.length; i++) {
|
|
1813
|
+
const value = lookup_array[i][0];
|
|
1814
|
+
|
|
1815
|
+
|
|
1816
|
+
if (typeof value === 'number') {
|
|
1817
|
+
|
|
1818
|
+
// check for exact match first, just in case
|
|
1819
|
+
if (value === lookup_value) {
|
|
1820
|
+
return ReturnIndex(i);
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
const delta = Math.abs(value - lookup_value);
|
|
1824
|
+
|
|
1825
|
+
if ((match_mode === 1 && value > lookup_value) || (match_mode === -1 && value < lookup_value)){
|
|
1826
|
+
if (index < 0 || delta < min_delta) {
|
|
1827
|
+
min_delta = delta;
|
|
1828
|
+
index = i;
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
if (index >= 0) {
|
|
1836
|
+
return ReturnIndex(index);
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
switch (match_mode) {
|
|
1842
|
+
|
|
1843
|
+
case 2:
|
|
1844
|
+
{
|
|
1845
|
+
// wildcard string match. we only handle strings for
|
|
1846
|
+
// this case (see above).
|
|
1847
|
+
|
|
1848
|
+
const pattern = Utils.ParseWildcards(lookup_value?.toString() || '');
|
|
1849
|
+
const regex = new RegExp('^' + pattern + '$', 'i'); //.exec(lookup_value);
|
|
1850
|
+
|
|
1851
|
+
for (let i = 0; i < lookup_array.length; i++) {
|
|
1852
|
+
const value = lookup_array[i][0];
|
|
1853
|
+
if (typeof value === 'string' && regex.exec(value)) {
|
|
1854
|
+
return ReturnIndex(i);
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
}
|
|
1859
|
+
break;
|
|
1860
|
+
|
|
1861
|
+
case 0:
|
|
1862
|
+
if (typeof lookup_value === 'string') {
|
|
1863
|
+
lookup_value = lookup_value.toLowerCase();
|
|
1864
|
+
}
|
|
1865
|
+
for (let i = 0; i < lookup_array.length; i++) {
|
|
1866
|
+
let value = lookup_array[i][0];
|
|
1867
|
+
|
|
1868
|
+
if (typeof value === 'string') {
|
|
1869
|
+
value = value.toLowerCase();
|
|
1870
|
+
}
|
|
1871
|
+
if (value === lookup_value) {
|
|
1872
|
+
return ReturnIndex(i);
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
break;
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
|
|
1880
|
+
// FIXME: if we're expecting to return an array maybe we should
|
|
1881
|
+
// pack it up as an array? if it's not already an array? (...)
|
|
1882
|
+
|
|
1883
|
+
return (not_found && not_found.type !== ValueType.undefined) ? not_found : NAError();
|
|
1884
|
+
|
|
1885
|
+
},
|
|
1886
|
+
},
|
|
1887
|
+
*/
|
|
1888
|
+
/**
|
|
1889
|
+
* copied from HLOOKUP, fix that one first
|
|
1890
|
+
*/
|
|
1891
|
+
HLookup: {
|
|
1892
|
+
arguments: [...zlookup_arguments],
|
|
1893
|
+
fn: (value, table, col, inexact = true) => {
|
|
1894
|
+
return ZLookup(value, table, col, inexact, true);
|
|
1895
|
+
},
|
|
1896
|
+
},
|
|
1897
|
+
/**
|
|
1898
|
+
* FIXME: does not implement inexact matching (what's the algo for
|
|
1899
|
+
* that, anyway? nearest? price is right style? what about ties?)
|
|
1900
|
+
*/
|
|
1901
|
+
VLookup: {
|
|
1902
|
+
arguments: [...zlookup_arguments],
|
|
1903
|
+
fn: (value, table, col, inexact = true) => {
|
|
1904
|
+
return ZLookup(value, table, col, inexact, false);
|
|
1905
|
+
},
|
|
1906
|
+
},
|
|
1907
|
+
Product: {
|
|
1908
|
+
arguments: [{
|
|
1909
|
+
boxed: true,
|
|
1910
|
+
name: 'values or ranges',
|
|
1911
|
+
repeat: true,
|
|
1912
|
+
}],
|
|
1913
|
+
fn: (...args) => {
|
|
1914
|
+
let product = { real: 1, imaginary: 0 };
|
|
1915
|
+
args = Utils.FlattenBoxed(args);
|
|
1916
|
+
for (const arg of args) {
|
|
1917
|
+
if (arg.type === ValueType.complex) {
|
|
1918
|
+
product = ComplexMultply(product, arg.value);
|
|
1919
|
+
}
|
|
1920
|
+
else if (arg.type === ValueType.number) {
|
|
1921
|
+
product.real *= arg.value;
|
|
1922
|
+
product.imaginary *= arg.value;
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
return ComplexOrReal(product);
|
|
1926
|
+
/*
|
|
1927
|
+
return { type: ValueType.number, value: Utils.Flatten(args).reduce((a: number, b: any) => {
|
|
1928
|
+
if (typeof b === 'undefined') return a;
|
|
1929
|
+
return a * Number(b);
|
|
1930
|
+
}, 1) };
|
|
1931
|
+
*/
|
|
1932
|
+
},
|
|
1933
|
+
},
|
|
1934
|
+
Log: {
|
|
1935
|
+
arguments: [{
|
|
1936
|
+
name: 'number',
|
|
1937
|
+
unroll: true
|
|
1938
|
+
}, {
|
|
1939
|
+
name: 'base',
|
|
1940
|
+
unroll: true
|
|
1941
|
+
}],
|
|
1942
|
+
/** default is base 10; allow specific base */
|
|
1943
|
+
fn: (a, base = 10) => {
|
|
1944
|
+
return { type: ValueType.number, value: Math.log(a) / Math.log(base) };
|
|
1945
|
+
},
|
|
1946
|
+
},
|
|
1947
|
+
Log10: {
|
|
1948
|
+
arguments: [{
|
|
1949
|
+
name: 'number',
|
|
1950
|
+
unroll: true,
|
|
1951
|
+
}],
|
|
1952
|
+
fn: (a) => {
|
|
1953
|
+
return { type: ValueType.number, value: Math.log(a) / Math.log(10) };
|
|
1954
|
+
},
|
|
1955
|
+
},
|
|
1956
|
+
Ln: {
|
|
1957
|
+
arguments: [{
|
|
1958
|
+
name: 'number',
|
|
1959
|
+
unroll: true,
|
|
1960
|
+
}],
|
|
1961
|
+
fn: (a) => {
|
|
1962
|
+
return { type: ValueType.number, value: Math.log(a) };
|
|
1963
|
+
},
|
|
1964
|
+
},
|
|
1965
|
+
'Ceiling.Math': {
|
|
1966
|
+
arguments: [{
|
|
1967
|
+
name: 'number',
|
|
1968
|
+
unroll: true
|
|
1969
|
+
}, {
|
|
1970
|
+
name: 'significance',
|
|
1971
|
+
unroll: true
|
|
1972
|
+
}, {
|
|
1973
|
+
name: 'away from zero',
|
|
1974
|
+
unroll: true,
|
|
1975
|
+
}],
|
|
1976
|
+
xlfn: true,
|
|
1977
|
+
fn: (a, significance = 1, mode) => {
|
|
1978
|
+
let value = 0;
|
|
1979
|
+
if (mode && a < 0) {
|
|
1980
|
+
value = -Math.ceil(-a / significance) * significance;
|
|
1981
|
+
}
|
|
1982
|
+
else {
|
|
1983
|
+
value = Math.ceil(a / significance) * significance;
|
|
1984
|
+
}
|
|
1985
|
+
return {
|
|
1986
|
+
type: ValueType.number,
|
|
1987
|
+
value,
|
|
1988
|
+
};
|
|
1989
|
+
},
|
|
1990
|
+
},
|
|
1991
|
+
'Floor.Math': {
|
|
1992
|
+
arguments: [{
|
|
1993
|
+
name: 'number',
|
|
1994
|
+
unroll: true
|
|
1995
|
+
}, {
|
|
1996
|
+
name: 'significance',
|
|
1997
|
+
unroll: true
|
|
1998
|
+
}, {
|
|
1999
|
+
name: 'away from zero',
|
|
2000
|
+
unroll: true,
|
|
2001
|
+
}],
|
|
2002
|
+
fn: (a, significance = 1, mode) => {
|
|
2003
|
+
let value = 0;
|
|
2004
|
+
if (mode && a < 0) {
|
|
2005
|
+
value = -Math.floor(-a / significance) * significance;
|
|
2006
|
+
}
|
|
2007
|
+
else {
|
|
2008
|
+
value = Math.floor(a / significance) * significance;
|
|
2009
|
+
}
|
|
2010
|
+
return {
|
|
2011
|
+
type: ValueType.number,
|
|
2012
|
+
value,
|
|
2013
|
+
};
|
|
2014
|
+
},
|
|
2015
|
+
},
|
|
2016
|
+
Floor: {
|
|
2017
|
+
arguments: [{
|
|
2018
|
+
name: 'number',
|
|
2019
|
+
unroll: true
|
|
2020
|
+
}, {
|
|
2021
|
+
name: 'significance',
|
|
2022
|
+
unroll: true
|
|
2023
|
+
}],
|
|
2024
|
+
fn: (a, significance = 1) => {
|
|
2025
|
+
return {
|
|
2026
|
+
type: ValueType.number,
|
|
2027
|
+
value: Math.floor(a / significance) * significance,
|
|
2028
|
+
};
|
|
2029
|
+
},
|
|
2030
|
+
},
|
|
2031
|
+
Ceiling: {
|
|
2032
|
+
arguments: [{
|
|
2033
|
+
name: 'number',
|
|
2034
|
+
unroll: true
|
|
2035
|
+
}, {
|
|
2036
|
+
name: 'significance',
|
|
2037
|
+
unroll: true
|
|
2038
|
+
}],
|
|
2039
|
+
fn: (a, significance = 1) => {
|
|
2040
|
+
return {
|
|
2041
|
+
type: ValueType.number,
|
|
2042
|
+
value: Math.ceil(a / significance) * significance,
|
|
2043
|
+
};
|
|
2044
|
+
},
|
|
2045
|
+
},
|
|
2046
|
+
Round: {
|
|
2047
|
+
arguments: [{ unroll: true }, { unroll: true }], // FIXME: lazy
|
|
2048
|
+
fn: (a, digits = 0) => {
|
|
2049
|
+
const m = Math.pow(10, digits);
|
|
2050
|
+
return {
|
|
2051
|
+
type: ValueType.number,
|
|
2052
|
+
value: Math.round(m * a) / m,
|
|
2053
|
+
};
|
|
2054
|
+
},
|
|
2055
|
+
},
|
|
2056
|
+
RoundDown: {
|
|
2057
|
+
arguments: [{ unroll: true }, { unroll: true }], // FIXME: lazy
|
|
2058
|
+
fn: (a, digits = 0) => {
|
|
2059
|
+
const m = Math.pow(10, digits);
|
|
2060
|
+
const positive = a >= 0;
|
|
2061
|
+
return {
|
|
2062
|
+
type: ValueType.number,
|
|
2063
|
+
value: positive ? Math.floor(m * a) / m : Math.ceil(m * a) / m,
|
|
2064
|
+
};
|
|
2065
|
+
},
|
|
2066
|
+
},
|
|
2067
|
+
RoundUp: {
|
|
2068
|
+
arguments: [{ unroll: true }, { unroll: true }], // FIXME: lazy
|
|
2069
|
+
fn: (a, digits = 0) => {
|
|
2070
|
+
const m = Math.pow(10, digits);
|
|
2071
|
+
const positive = a >= 0;
|
|
2072
|
+
return {
|
|
2073
|
+
type: ValueType.number,
|
|
2074
|
+
value: positive ? Math.ceil(m * a) / m : Math.floor(m * a) / m,
|
|
2075
|
+
};
|
|
2076
|
+
},
|
|
2077
|
+
},
|
|
2078
|
+
/*
|
|
2079
|
+
|
|
2080
|
+
Round: {
|
|
2081
|
+
description: 'Round to a specified number of digits',
|
|
2082
|
+
|
|
2083
|
+
/ ** round with variable digits * /
|
|
2084
|
+
fn: (value: number, digits = 0) => {
|
|
2085
|
+
const m = Math.pow(10, digits);
|
|
2086
|
+
return Math.round(m * value) / m;
|
|
2087
|
+
},
|
|
2088
|
+
},
|
|
2089
|
+
|
|
2090
|
+
RoundDown: {
|
|
2091
|
+
/ ** round down with variable digits * /
|
|
2092
|
+
fn: (value: number, digits = 0) => {
|
|
2093
|
+
digits = Math.max(0, digits);
|
|
2094
|
+
const m = Math.pow(10, digits);
|
|
2095
|
+
return Math.floor(m * value) / m;
|
|
2096
|
+
},
|
|
2097
|
+
},
|
|
2098
|
+
|
|
2099
|
+
|
|
2100
|
+
*/
|
|
2101
|
+
Reverse: {
|
|
2102
|
+
arguments: [
|
|
2103
|
+
{ boxed: true },
|
|
2104
|
+
],
|
|
2105
|
+
fn: (a) => {
|
|
2106
|
+
/*
|
|
2107
|
+
|
|
2108
|
+
what is this? this would do anything useful
|
|
2109
|
+
...oh I see, it reverses along one axis or the other
|
|
2110
|
+
|
|
2111
|
+
if ( Array.isArray(a)) {
|
|
2112
|
+
if (a.length === 1 ) return [a[0].reverse()];
|
|
2113
|
+
return a.reverse();
|
|
2114
|
+
}
|
|
2115
|
+
*/
|
|
2116
|
+
if (a.type === ValueType.array) {
|
|
2117
|
+
if (a.value.length === 1) {
|
|
2118
|
+
a.value[0].reverse();
|
|
2119
|
+
}
|
|
2120
|
+
else {
|
|
2121
|
+
a.value.reverse();
|
|
2122
|
+
}
|
|
2123
|
+
return a;
|
|
2124
|
+
}
|
|
2125
|
+
return {
|
|
2126
|
+
type: ValueType.string,
|
|
2127
|
+
value: (a.value ?? '').toString().split('').reverse().join(''),
|
|
2128
|
+
};
|
|
2129
|
+
},
|
|
2130
|
+
},
|
|
2131
|
+
/**
|
|
2132
|
+
* exp was not broken out, but added so we can support complex numbers.
|
|
2133
|
+
*/
|
|
2134
|
+
Exp: {
|
|
2135
|
+
arguments: [
|
|
2136
|
+
{ boxed: true, unroll: true },
|
|
2137
|
+
],
|
|
2138
|
+
fn: (x) => {
|
|
2139
|
+
if (x.type === ValueType.complex) {
|
|
2140
|
+
const value = ComplexExp(x.value);
|
|
2141
|
+
return ComplexOrReal(value);
|
|
2142
|
+
}
|
|
2143
|
+
if (x.type !== ValueType.number) {
|
|
2144
|
+
return ValueError();
|
|
2145
|
+
}
|
|
2146
|
+
return { type: ValueType.number, value: Math.exp(x.value) };
|
|
2147
|
+
},
|
|
2148
|
+
},
|
|
2149
|
+
/**
|
|
2150
|
+
* abs was already broken out so we could support array application,
|
|
2151
|
+
* then updated to support complex numbers.
|
|
2152
|
+
*/
|
|
2153
|
+
Abs: {
|
|
2154
|
+
arguments: [
|
|
2155
|
+
{ boxed: true, unroll: true },
|
|
2156
|
+
],
|
|
2157
|
+
fn: (a) => {
|
|
2158
|
+
if (a.type === ValueType.complex) {
|
|
2159
|
+
return {
|
|
2160
|
+
type: ValueType.number,
|
|
2161
|
+
value: Math.sqrt(a.value.real * a.value.real + a.value.imaginary * a.value.imaginary),
|
|
2162
|
+
};
|
|
2163
|
+
}
|
|
2164
|
+
if (a.type !== ValueType.number) {
|
|
2165
|
+
return ValueError();
|
|
2166
|
+
}
|
|
2167
|
+
return { type: ValueType.number, value: Math.abs(a.value || 0) };
|
|
2168
|
+
},
|
|
2169
|
+
},
|
|
2170
|
+
Simplify: {
|
|
2171
|
+
arguments: [
|
|
2172
|
+
{ name: 'value', unroll: true, },
|
|
2173
|
+
{ name: 'significant digits', unroll: true, },
|
|
2174
|
+
],
|
|
2175
|
+
fn: (value, significant_digits = 2) => {
|
|
2176
|
+
significant_digits = significant_digits || 2;
|
|
2177
|
+
if (value === 0) {
|
|
2178
|
+
return { type: ValueType.number, value };
|
|
2179
|
+
}
|
|
2180
|
+
const negative = value < 0 ? -1 : 1;
|
|
2181
|
+
value *= negative;
|
|
2182
|
+
const x = Math.pow(10, Math.floor(Math.log10(value)) + 1 - significant_digits);
|
|
2183
|
+
return {
|
|
2184
|
+
type: ValueType.number,
|
|
2185
|
+
value: Math.round(value / x) * x * negative
|
|
2186
|
+
};
|
|
2187
|
+
},
|
|
2188
|
+
},
|
|
2189
|
+
Sqrt: {
|
|
2190
|
+
description: 'Returns the square root of the argument',
|
|
2191
|
+
arguments: [
|
|
2192
|
+
{ boxed: true, unroll: true },
|
|
2193
|
+
],
|
|
2194
|
+
fn: (ref) => {
|
|
2195
|
+
// little bit torn on this. what should sqrt(-1) return? a complex
|
|
2196
|
+
// number, or NaN? or should we control that with a flag?
|
|
2197
|
+
// UPDATE: now optional, see AltFunctionLibrary
|
|
2198
|
+
if (ref.type === ValueType.complex) {
|
|
2199
|
+
const value = ComplexPower(ref.value, { real: 0.5, imaginary: 0 });
|
|
2200
|
+
return ComplexOrReal(value);
|
|
2201
|
+
}
|
|
2202
|
+
else if (ref.type === ValueType.undefined || !ref.value) {
|
|
2203
|
+
return {
|
|
2204
|
+
type: ValueType.number, value: 0,
|
|
2205
|
+
};
|
|
2206
|
+
}
|
|
2207
|
+
/*
|
|
2208
|
+
else if (ref.type === ValueType.number && ref.value < 0) {
|
|
2209
|
+
const value = ComplexPower({real: ref.value, imaginary: 0}, {real: 0.5, imaginary: 0});
|
|
2210
|
+
return {
|
|
2211
|
+
type: ValueType.complex,
|
|
2212
|
+
value,
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
*/
|
|
2216
|
+
else if (ref.type === ValueType.number) {
|
|
2217
|
+
return {
|
|
2218
|
+
type: ValueType.number, value: Math.sqrt(ref.value),
|
|
2219
|
+
};
|
|
2220
|
+
}
|
|
2221
|
+
return ValueError();
|
|
2222
|
+
/*
|
|
2223
|
+
else {
|
|
2224
|
+
const value = Math.sqrt(ref.value);
|
|
2225
|
+
if (isNaN(value)) {
|
|
2226
|
+
return ValueError();
|
|
2227
|
+
}
|
|
2228
|
+
return { type: ValueType.number, value };
|
|
2229
|
+
}
|
|
2230
|
+
*/
|
|
2231
|
+
},
|
|
2232
|
+
},
|
|
2233
|
+
HexToDec: {
|
|
2234
|
+
arguments: [{ description: 'hexadecimal string', unroll: true }],
|
|
2235
|
+
fn: (hex) => {
|
|
2236
|
+
return { type: ValueType.number, value: parseInt(hex, 16) };
|
|
2237
|
+
},
|
|
2238
|
+
},
|
|
2239
|
+
DecToHex: {
|
|
2240
|
+
arguments: [{ description: 'number', unroll: true }],
|
|
2241
|
+
fn: (num) => {
|
|
2242
|
+
return { type: ValueType.string, value: num.toString(16) };
|
|
2243
|
+
},
|
|
2244
|
+
},
|
|
2245
|
+
Checkbox: {
|
|
2246
|
+
arguments: [
|
|
2247
|
+
{ name: 'checked' },
|
|
2248
|
+
],
|
|
2249
|
+
click: ClickCheckbox,
|
|
2250
|
+
render: RenderCheckbox,
|
|
2251
|
+
fn: (checked) => {
|
|
2252
|
+
return { value: !!checked, type: ValueType.boolean, };
|
|
2253
|
+
},
|
|
2254
|
+
},
|
|
2255
|
+
'Sparkline.Column': {
|
|
2256
|
+
arguments: [
|
|
2257
|
+
{ name: 'data' },
|
|
2258
|
+
{ name: 'color' },
|
|
2259
|
+
{ name: 'negative color' }
|
|
2260
|
+
],
|
|
2261
|
+
render: (options) => {
|
|
2262
|
+
Sparkline.RenderColumn(options.width, options.height, options.context, options.cell, options.style);
|
|
2263
|
+
return { handled: true }; // painted
|
|
2264
|
+
},
|
|
2265
|
+
fn: (...args) => {
|
|
2266
|
+
return { type: ValueType.object, value: args, key: 'sparkline-data', source: 'sparkline.column' };
|
|
2267
|
+
},
|
|
2268
|
+
},
|
|
2269
|
+
'Sparkline.Line': {
|
|
2270
|
+
arguments: [
|
|
2271
|
+
{ name: 'data' },
|
|
2272
|
+
{ name: 'color' },
|
|
2273
|
+
{ name: 'line width' },
|
|
2274
|
+
],
|
|
2275
|
+
render: (options) => {
|
|
2276
|
+
Sparkline.RenderLine(options.width, options.height, options.context, options.cell, options.style);
|
|
2277
|
+
return { handled: true }; // painted
|
|
2278
|
+
},
|
|
2279
|
+
fn: (...args) => {
|
|
2280
|
+
return { type: ValueType.object, value: args, key: 'sparkline-data', source: 'sparkline.line' };
|
|
2281
|
+
},
|
|
2282
|
+
},
|
|
2283
|
+
UniqueValues: {
|
|
2284
|
+
arguments: [
|
|
2285
|
+
{ name: 'range', boxed: true },
|
|
2286
|
+
],
|
|
2287
|
+
visibility: 'internal',
|
|
2288
|
+
fn: (area) => {
|
|
2289
|
+
if (area.type === ValueType.array) {
|
|
2290
|
+
// const cols = area.value.length;
|
|
2291
|
+
// const rows = area.value[0]?.length;
|
|
2292
|
+
// how is uniqueness defined in this context? (...)
|
|
2293
|
+
const Normalize = (cell) => {
|
|
2294
|
+
if (!cell) {
|
|
2295
|
+
return '';
|
|
2296
|
+
}
|
|
2297
|
+
else
|
|
2298
|
+
switch (cell.type) {
|
|
2299
|
+
case ValueType.string:
|
|
2300
|
+
case ValueType.number:
|
|
2301
|
+
case ValueType.boolean:
|
|
2302
|
+
return cell.value;
|
|
2303
|
+
case ValueType.undefined:
|
|
2304
|
+
return '';
|
|
2305
|
+
default:
|
|
2306
|
+
console.info("check", cell, cell.value);
|
|
2307
|
+
return cell.value?.toString() || '';
|
|
2308
|
+
}
|
|
2309
|
+
};
|
|
2310
|
+
const set = new Set();
|
|
2311
|
+
const duplicates = new Set();
|
|
2312
|
+
for (const column of area.value) {
|
|
2313
|
+
for (const cell of column) {
|
|
2314
|
+
const normalized = Normalize(cell);
|
|
2315
|
+
if (set.has(normalized)) {
|
|
2316
|
+
duplicates.add(normalized);
|
|
2317
|
+
}
|
|
2318
|
+
else {
|
|
2319
|
+
set.add(normalized);
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
const result = [];
|
|
2324
|
+
for (const column of area.value) {
|
|
2325
|
+
const column_result = [];
|
|
2326
|
+
for (const cell of column) {
|
|
2327
|
+
const value = Normalize(cell);
|
|
2328
|
+
column_result.push({
|
|
2329
|
+
type: ValueType.boolean,
|
|
2330
|
+
value: !duplicates.has(value),
|
|
2331
|
+
});
|
|
2332
|
+
}
|
|
2333
|
+
result.push(column_result);
|
|
2334
|
+
}
|
|
2335
|
+
return {
|
|
2336
|
+
type: ValueType.array,
|
|
2337
|
+
value: result,
|
|
2338
|
+
};
|
|
2339
|
+
}
|
|
2340
|
+
// if it's not an array, by definition it's unique
|
|
2341
|
+
return {
|
|
2342
|
+
type: ValueType.boolean,
|
|
2343
|
+
value: true,
|
|
2344
|
+
};
|
|
2345
|
+
},
|
|
2346
|
+
},
|
|
2347
|
+
Between: {
|
|
2348
|
+
arguments: [
|
|
2349
|
+
{ name: 'target', boxed: true, unroll: true },
|
|
2350
|
+
{ name: 'min' },
|
|
2351
|
+
{ name: 'max' },
|
|
2352
|
+
],
|
|
2353
|
+
visibility: 'internal',
|
|
2354
|
+
fn: (target, min = 0, max = 1) => {
|
|
2355
|
+
return {
|
|
2356
|
+
type: ValueType.boolean,
|
|
2357
|
+
value: (target.type === ValueType.number) && (target.value >= min && target.value <= max),
|
|
2358
|
+
};
|
|
2359
|
+
}
|
|
2360
|
+
},
|
|
2361
|
+
Gradient: {
|
|
2362
|
+
arguments: [
|
|
2363
|
+
{ name: 'range', boxed: true },
|
|
2364
|
+
{ name: 'min', },
|
|
2365
|
+
{ name: 'max', },
|
|
2366
|
+
/*
|
|
2367
|
+
* data bars use gradients but have to report zero. also we explicitly
|
|
2368
|
+
* set 0 as min/max for data bars, although you can override
|
|
2369
|
+
*/
|
|
2370
|
+
{ name: 'parameters' },
|
|
2371
|
+
],
|
|
2372
|
+
visibility: 'internal',
|
|
2373
|
+
fn: (area, static_min, static_max, parameters) => {
|
|
2374
|
+
const tmp = Utils.FlattenBoxed([area]);
|
|
2375
|
+
// let sum = 0;
|
|
2376
|
+
let count = 0;
|
|
2377
|
+
let min = 0;
|
|
2378
|
+
let max = 0;
|
|
2379
|
+
for (const ref of tmp) {
|
|
2380
|
+
if (ref.type === ValueType.error) {
|
|
2381
|
+
return ref;
|
|
2382
|
+
}
|
|
2383
|
+
if (ref.type === ValueType.number) {
|
|
2384
|
+
if (count === 0 && !parameters) { // leave 0 min, max for data bars
|
|
2385
|
+
min = ref.value;
|
|
2386
|
+
max = ref.value;
|
|
2387
|
+
}
|
|
2388
|
+
else {
|
|
2389
|
+
min = Math.min(min, ref.value);
|
|
2390
|
+
max = Math.max(max, ref.value);
|
|
2391
|
+
}
|
|
2392
|
+
count++;
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
/*
|
|
2396
|
+
if (parameters) {
|
|
2397
|
+
if (min > 0) { min = 0; }
|
|
2398
|
+
if (max < 0) { max = 0; }
|
|
2399
|
+
}
|
|
2400
|
+
*/
|
|
2401
|
+
if (typeof static_max === 'number') {
|
|
2402
|
+
max = static_max;
|
|
2403
|
+
}
|
|
2404
|
+
if (typeof static_min === 'number') {
|
|
2405
|
+
min = static_min;
|
|
2406
|
+
}
|
|
2407
|
+
const range = max - min;
|
|
2408
|
+
let rows = 1;
|
|
2409
|
+
let columns = 1;
|
|
2410
|
+
if (area.type === ValueType.array) {
|
|
2411
|
+
rows = area.value.length;
|
|
2412
|
+
columns = area.value[0]?.length || 0;
|
|
2413
|
+
const result = [];
|
|
2414
|
+
for (let r = 0; r < rows; r++) {
|
|
2415
|
+
const row = [];
|
|
2416
|
+
for (let c = 0; c < columns; c++) {
|
|
2417
|
+
const src = area.value[r][c];
|
|
2418
|
+
if (src.type === ValueType.number) {
|
|
2419
|
+
let calc = 0;
|
|
2420
|
+
// special case: max === min. this can be used to do binary
|
|
2421
|
+
// coloring over a set of data (ignoring the pivot).
|
|
2422
|
+
// FIXME: use a separate loop?
|
|
2423
|
+
if (max === min) {
|
|
2424
|
+
if (src.value > max) {
|
|
2425
|
+
calc = 1;
|
|
2426
|
+
}
|
|
2427
|
+
else if (src.value < max) {
|
|
2428
|
+
calc = 0;
|
|
2429
|
+
}
|
|
2430
|
+
else {
|
|
2431
|
+
calc = 0.5;
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
else if (range > 0) {
|
|
2435
|
+
calc = (src.value - min) / range;
|
|
2436
|
+
}
|
|
2437
|
+
if (parameters) {
|
|
2438
|
+
row.push({ type: ValueType.array, value: [[
|
|
2439
|
+
{ type: ValueType.number, value: calc },
|
|
2440
|
+
{ type: ValueType.number, value: (0 - min) / range }, // zero
|
|
2441
|
+
]] });
|
|
2442
|
+
}
|
|
2443
|
+
else {
|
|
2444
|
+
row.push({ type: ValueType.number, value: calc });
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2447
|
+
else {
|
|
2448
|
+
row.push({ type: ValueType.undefined });
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2451
|
+
result.push(row);
|
|
2452
|
+
}
|
|
2453
|
+
return { type: ValueType.array, value: result };
|
|
2454
|
+
}
|
|
2455
|
+
else {
|
|
2456
|
+
return ArgumentError();
|
|
2457
|
+
}
|
|
2458
|
+
},
|
|
2459
|
+
},
|
|
2460
|
+
ATan2: {
|
|
2461
|
+
arguments: [
|
|
2462
|
+
{ name: 'y', boxed: true, unroll: true },
|
|
2463
|
+
{ name: 'x', boxed: true, unroll: true },
|
|
2464
|
+
],
|
|
2465
|
+
fn: (y, x) => {
|
|
2466
|
+
if (y.type === ValueType.number && x.type === ValueType.number) {
|
|
2467
|
+
return { type: ValueType.number, value: Math.atan2(y.value, x.value) };
|
|
2468
|
+
}
|
|
2469
|
+
if (y.type === ValueType.number) {
|
|
2470
|
+
y = { type: ValueType.complex, value: { real: y.value, imaginary: 0 } };
|
|
2471
|
+
}
|
|
2472
|
+
if (x.type === ValueType.number) {
|
|
2473
|
+
x = { type: ValueType.complex, value: { real: x.value, imaginary: 0 } };
|
|
2474
|
+
}
|
|
2475
|
+
if (y.type === ValueType.complex && x.type === ValueType.complex) {
|
|
2476
|
+
const value = ComplexMath.ATan2(y.value, x.value);
|
|
2477
|
+
// should we have an error for infinities? not sure. not sure
|
|
2478
|
+
// why this hasn't come up before...
|
|
2479
|
+
if (value === false) {
|
|
2480
|
+
return ArgumentError();
|
|
2481
|
+
}
|
|
2482
|
+
return {
|
|
2483
|
+
type: ValueType.complex, value,
|
|
2484
|
+
};
|
|
2485
|
+
}
|
|
2486
|
+
return ArgumentError();
|
|
2487
|
+
},
|
|
2488
|
+
},
|
|
2489
|
+
Sin: TrigFunction(Math.sin, ComplexMath.Sin),
|
|
2490
|
+
SinH: TrigFunction(Math.sinh, ComplexMath.SinH),
|
|
2491
|
+
ASin: TrigFunction(Math.asin, ComplexMath.ASin),
|
|
2492
|
+
Cos: TrigFunction(Math.cos, ComplexMath.Cos),
|
|
2493
|
+
CosH: TrigFunction(Math.cosh, ComplexMath.CosH),
|
|
2494
|
+
ACos: TrigFunction(Math.acos, ComplexMath.ACos),
|
|
2495
|
+
Tan: TrigFunction(Math.tan, ComplexMath.Tan),
|
|
2496
|
+
TanH: TrigFunction(Math.tanh, ComplexMath.TanH),
|
|
2497
|
+
ATan: TrigFunction(Math.atan, ComplexMath.ATan),
|
|
2498
|
+
E: { fn: () => { return { type: ValueType.number, value: Math.E }; } },
|
|
2499
|
+
PI: { fn: () => { return { type: ValueType.number, value: Math.PI }; } },
|
|
2500
|
+
SQRT2: { fn: () => { return { type: ValueType.number, value: Math.SQRT2 }; } },
|
|
2501
|
+
SQRT1_2: { fn: () => { return { type: ValueType.number, value: Math.SQRT1_2 }; } },
|
|
2502
|
+
Sequence: {
|
|
2503
|
+
arguments: [
|
|
2504
|
+
{ name: 'rows', boxed: true },
|
|
2505
|
+
{ name: 'columns', default: 1, boxed: true },
|
|
2506
|
+
{ name: 'start', default: 1, boxed: true },
|
|
2507
|
+
{ name: 'step', default: 1, boxed: true }
|
|
2508
|
+
],
|
|
2509
|
+
fn: (rows, columns, start, step) => {
|
|
2510
|
+
const rx = NumberArgument(rows, 1);
|
|
2511
|
+
const cx = NumberArgument(columns, 1);
|
|
2512
|
+
const step_ = NumberArgument(step, 1);
|
|
2513
|
+
const start_ = NumberArgument(start, 1);
|
|
2514
|
+
if (rx === false || cx === false || step_ === false || start_ === false) {
|
|
2515
|
+
return ArgumentError();
|
|
2516
|
+
}
|
|
2517
|
+
const value = [];
|
|
2518
|
+
for (let c = 0; c < cx; c++) {
|
|
2519
|
+
const col = [];
|
|
2520
|
+
for (let r = 0; r < rx; r++) {
|
|
2521
|
+
col.push({ type: ValueType.number, value: start_ + r * step_ * cx + c * step_ });
|
|
2522
|
+
}
|
|
2523
|
+
value.push(col);
|
|
2524
|
+
}
|
|
2525
|
+
return { type: ValueType.array, value };
|
|
2526
|
+
},
|
|
2527
|
+
},
|
|
2528
|
+
};
|
|
2529
|
+
// alias
|
|
2530
|
+
// add functions from Math (intrinsic), unless the name overlaps
|
|
2531
|
+
// with something already in there
|
|
2532
|
+
// we need to construct a separate map to match icase (this is now
|
|
2533
|
+
// even more useful since we have a separate section for aliases)
|
|
2534
|
+
const name_map = {};
|
|
2535
|
+
for (const key of Object.keys(BaseFunctionLibrary)) {
|
|
2536
|
+
name_map[key.toLowerCase()] = key;
|
|
2537
|
+
}
|
|
2538
|
+
// block these names from auto-import from Math
|
|
2539
|
+
const block_list = [
|
|
2540
|
+
'ceil',
|
|
2541
|
+
'pow',
|
|
2542
|
+
'ln10',
|
|
2543
|
+
'ln2',
|
|
2544
|
+
'log10',
|
|
2545
|
+
'log10e',
|
|
2546
|
+
'log1p',
|
|
2547
|
+
'log2',
|
|
2548
|
+
'log2e',
|
|
2549
|
+
'random',
|
|
2550
|
+
'imul',
|
|
2551
|
+
'clz32',
|
|
2552
|
+
'fround',
|
|
2553
|
+
'f16round',
|
|
2554
|
+
];
|
|
2555
|
+
const block_map = {};
|
|
2556
|
+
for (const entry of block_list) {
|
|
2557
|
+
block_map[entry.toLowerCase()] = entry;
|
|
2558
|
+
}
|
|
2559
|
+
for (const name of Object.getOwnPropertyNames(Math)) {
|
|
2560
|
+
// check if it exists (we have already registered something
|
|
2561
|
+
// with the same name) -- don't override existing
|
|
2562
|
+
const lc = name.toLowerCase();
|
|
2563
|
+
if (name_map[lc]) {
|
|
2564
|
+
continue;
|
|
2565
|
+
}
|
|
2566
|
+
// also explicitly block some names we don't want to include (pow vs. power, etc)
|
|
2567
|
+
if (block_map[lc]) {
|
|
2568
|
+
continue;
|
|
2569
|
+
}
|
|
2570
|
+
const descriptor = Object.getOwnPropertyDescriptor(Math, name);
|
|
2571
|
+
if (!descriptor) {
|
|
2572
|
+
continue;
|
|
2573
|
+
}
|
|
2574
|
+
const value = descriptor.value;
|
|
2575
|
+
const type = typeof (value);
|
|
2576
|
+
switch (type) {
|
|
2577
|
+
case 'number':
|
|
2578
|
+
// console.info("MATH CONSTANT", name);
|
|
2579
|
+
BaseFunctionLibrary[name] = {
|
|
2580
|
+
fn: () => {
|
|
2581
|
+
return { type: ValueType.number, value };
|
|
2582
|
+
},
|
|
2583
|
+
category: ['Math Functions'],
|
|
2584
|
+
};
|
|
2585
|
+
break;
|
|
2586
|
+
case 'function':
|
|
2587
|
+
// console.info("MATH FUNC", name);
|
|
2588
|
+
BaseFunctionLibrary[name] = {
|
|
2589
|
+
// description: 'Math function',
|
|
2590
|
+
fn: (...args) => {
|
|
2591
|
+
return Box(value(...args));
|
|
2592
|
+
},
|
|
2593
|
+
category: ['Math Functions'],
|
|
2594
|
+
};
|
|
2595
|
+
break;
|
|
2596
|
+
default:
|
|
2597
|
+
console.info('unexpected type:', type, name);
|
|
2598
|
+
break;
|
|
2599
|
+
}
|
|
2600
|
+
}
|
|
2601
|
+
// IE11: patch log10 function // FIXME: is this necessary anymore?
|
|
2602
|
+
if (!Math.log10) {
|
|
2603
|
+
Math.log10 = (a) => Math.log(a) / Math.log(10);
|
|
2604
|
+
/*
|
|
2605
|
+
BaseFunctionLibrary.log10 = {
|
|
2606
|
+
fn: (x) => Math.log(x) / Math.log(10),
|
|
2607
|
+
category: ['Math Functions'],
|
|
2608
|
+
};
|
|
2609
|
+
*/
|
|
2610
|
+
}
|
|
2611
|
+
//# sourceMappingURL=base-functions.js.map
|