@svgrid/enterprise 3.0.2 → 3.1.0
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/LICENSE +94 -24
- package/README.md +36 -4
- package/dist/SvAdvancedFilter.svelte +1 -1
- package/dist/SvAlertsManager.svelte +2 -1
- package/dist/SvBoard.svelte +2 -1
- package/dist/SvExportMenu.svelte +4 -1
- package/dist/SvExportMenu.svelte.d.ts +0 -13
- package/dist/SvExpressionEditor.svelte +3 -3
- package/dist/SvFormulaBar.svelte +94 -20
- package/dist/SvFormulaBar.svelte.d.ts +7 -0
- package/dist/SvGridAlerts.svelte +49 -9
- package/dist/SvGridBoard.svelte +13 -8
- package/dist/SvGridBulkEditDrawer.svelte +26 -2
- package/dist/SvGridEditPanel.svelte +3 -0
- package/dist/SvGridScheduler.svelte +69 -40
- package/dist/SvGridSelectionBar.svelte +10 -3
- package/dist/SvPivotDesigner.svelte +198 -40
- package/dist/SvPivotDesigner.svelte.d.ts +27 -20
- package/dist/SvSheet.svelte +4043 -243
- package/dist/SvSheet.svelte.d.ts +76 -3
- package/dist/SvSheetChartSetup.svelte +151 -0
- package/dist/SvSheetChartSetup.svelte.d.ts +18 -0
- package/dist/SvSheetComment.svelte +216 -31
- package/dist/SvSheetComment.svelte.d.ts +11 -11
- package/dist/SvSheetConditionalFormat.svelte +28 -31
- package/dist/SvSheetDataValidation.svelte +66 -42
- package/dist/SvSheetEditRanges.svelte +217 -0
- package/dist/SvSheetEditRanges.svelte.d.ts +15 -0
- package/dist/SvSheetErrors.svelte +125 -0
- package/dist/SvSheetErrors.svelte.d.ts +20 -0
- package/dist/SvSheetEvaluate.svelte +133 -0
- package/dist/SvSheetEvaluate.svelte.d.ts +15 -0
- package/dist/SvSheetFilterMenu.svelte +137 -34
- package/dist/SvSheetFilterMenu.svelte.d.ts +5 -0
- package/dist/SvSheetFindReplace.svelte +19 -19
- package/dist/SvSheetFormatCells.svelte +125 -81
- package/dist/SvSheetGoalSeek.svelte +17 -16
- package/dist/SvSheetInsertFunction.svelte +15 -13
- package/dist/SvSheetIteration.svelte +81 -0
- package/dist/SvSheetIteration.svelte.d.ts +10 -0
- package/dist/SvSheetLink.svelte +99 -0
- package/dist/SvSheetLink.svelte.d.ts +17 -0
- package/dist/SvSheetListPicker.svelte +4 -2
- package/dist/SvSheetListPicker.svelte.d.ts +0 -6
- package/dist/SvSheetManageRules.svelte +20 -18
- package/dist/SvSheetNameManager.svelte +17 -15
- package/dist/SvSheetPageSetup.svelte +159 -0
- package/dist/SvSheetPageSetup.svelte.d.ts +13 -0
- package/dist/SvSheetPasteSpecial.svelte +22 -20
- package/dist/SvSheetPivot.svelte +220 -0
- package/dist/SvSheetPivot.svelte.d.ts +21 -0
- package/dist/SvSheetProtectSheet.svelte +81 -0
- package/dist/SvSheetProtectSheet.svelte.d.ts +11 -0
- package/dist/SvSheetRemoveDuplicates.svelte +13 -10
- package/dist/SvSheetRibbon.svelte +104 -51
- package/dist/SvSheetSizeDialog.svelte +4 -2
- package/dist/SvSheetSort.svelte +218 -0
- package/dist/SvSheetSort.svelte.d.ts +28 -0
- package/dist/SvSheetSparklines.svelte +135 -0
- package/dist/SvSheetSparklines.svelte.d.ts +14 -0
- package/dist/SvSheetTable.svelte +160 -0
- package/dist/SvSheetTable.svelte.d.ts +24 -0
- package/dist/SvSheetTabs.svelte +161 -31
- package/dist/SvSheetTabs.svelte.d.ts +22 -8
- package/dist/SvSheetTextToColumns.svelte +20 -20
- package/dist/SvSheetValidationAlert.svelte +8 -6
- package/dist/advanced-filter-enable.js +1 -1
- package/dist/alerts/alert-engine.d.ts +6 -0
- package/dist/alerts/alert-engine.js +21 -4
- package/dist/alerts/alert-formats.js +2 -0
- package/dist/alerts/alert-types.d.ts +1 -1
- package/dist/alerts.d.ts +1 -1
- package/dist/alerts.js +2 -2
- package/dist/board.js +2 -2
- package/dist/bulk-edit.d.ts +3 -1
- package/dist/bulk-edit.js +1 -1
- package/dist/cdn/svgrid-enterprise.svelte-external.js +39827 -16429
- package/dist/designer/assets/GridMenus-8gTNssgV.js +8 -0
- package/dist/designer/assets/{SvChartMenu-D9hEeHar.js → SvChartMenu-BDoeqGz6.js} +1 -1
- package/dist/designer/assets/{SvDateTimePicker-DARvKUL2.js → SvDateTimePicker-DCPg7WGN.js} +1 -1
- package/dist/designer/assets/SvGridCellEditor-DMoWJjEI.js +4 -0
- package/dist/designer/assets/{SvGridChart-B8j1SSy3.js → SvGridChart-Cy1ATrWh.js} +2 -2
- package/dist/designer/assets/SvGridChartBuilder-eQ3mn0L9.js +1 -0
- package/dist/designer/assets/{SvGridChartPanel-pT6ITc2P.js → SvGridChartPanel-CATdVc9X.js} +3 -3
- package/dist/designer/assets/{SvGridChartView-bg281UA1.js → SvGridChartView-CmsZtCED.js} +1 -1
- package/dist/designer/assets/SvGridDropdown-Cjz0H-fI.js +1 -0
- package/dist/designer/assets/{SvTabs-UFCMTHXS.js → SvTabs-2mHC6oaz.js} +1 -1
- package/dist/designer/assets/cell-formatting-BbubJTVz.js +1 -0
- package/dist/designer/assets/{chart-cIPUsbM5.js → chart-Cn7MnNqA.js} +1 -1
- package/dist/designer/assets/{chart-panel-messages-DtTxpBUU.js → chart-panel-messages-Dz7PTIqT.js} +1 -1
- package/dist/designer/assets/{chart-summary-Czu_5epb.js → chart-summary-BD1qYB2R.js} +1 -1
- package/dist/designer/assets/{chart-trend-D5I5VEe8.js → chart-trend-B8xr5LuD.js} +1 -1
- package/dist/designer/assets/{dismissable-BO547MH9.js → dismissable-ChWRPkoa.js} +1 -1
- package/dist/designer/assets/{export-format-D-eP4b2l.js → export-format-DGckmgKr.js} +1 -1
- package/dist/designer/assets/{focus-trap-CAreAhlU.js → focus-trap-V2-6Fy9I.js} +1 -1
- package/dist/designer/assets/{index-DwU7CAMm.js → index-BSjv6Qec.js} +317 -315
- package/dist/designer/assets/index-_JVFxt02.css +1 -0
- package/dist/designer/assets/row-resize-BInFWCpr.js +1 -0
- package/dist/designer/assets/server-block-cache-CXJKkJpy.js +1 -0
- package/dist/designer/assets/src-DUQ4xE3x.js +58 -0
- package/dist/designer/assets/validate-C3B8QKsM.js +1 -0
- package/dist/designer/index.html +17 -16
- package/dist/export-pdf.d.ts +75 -0
- package/dist/export-pdf.js +216 -15
- package/dist/export.js +10 -2
- package/dist/gantt/SvGridGantt.svelte +2890 -0
- package/dist/gantt/SvGridGantt.svelte.d.ts +30 -0
- package/dist/gantt/gantt-config.d.ts +73 -0
- package/dist/gantt/gantt-config.js +1 -0
- package/dist/gantt/gantt-critical-path.d.ts +67 -0
- package/dist/gantt/gantt-critical-path.js +207 -0
- package/dist/gantt/gantt-model.d.ts +257 -0
- package/dist/gantt/gantt-model.js +651 -0
- package/dist/gantt/gantt-resources.d.ts +59 -0
- package/dist/gantt/gantt-resources.js +71 -0
- package/dist/gantt/gantt.d.ts +7 -0
- package/dist/gantt/gantt.js +36 -0
- package/dist/gantt/index.d.ts +15 -0
- package/dist/gantt/index.js +22 -0
- package/dist/gantt/timeline-arrows.d.ts +102 -0
- package/dist/gantt/timeline-arrows.js +181 -0
- package/dist/import.d.ts +2 -2
- package/dist/import.js +1 -1
- package/dist/index.d.ts +34 -7
- package/dist/index.js +44 -7
- package/dist/install.js +8 -1
- package/dist/license-core.d.ts +17 -1
- package/dist/license-core.js +16 -5
- package/dist/license.d.ts +18 -2
- package/dist/license.js +71 -6
- package/dist/node/studio.js +140 -8
- package/dist/pivot-enable.js +2 -2
- package/dist/scheduler-dependencies.d.ts +35 -0
- package/dist/scheduler-dependencies.js +36 -1
- package/dist/scheduler.js +2 -2
- package/dist/selection-bar.js +2 -2
- package/dist/server/SvGroupCell.svelte +213 -0
- package/dist/server/SvGroupCell.svelte.d.ts +62 -0
- package/dist/server/SvRowGroupPanel.svelte +322 -0
- package/dist/server/SvRowGroupPanel.svelte.d.ts +37 -0
- package/dist/server/enable.d.ts +17 -0
- package/dist/server/enable.js +49 -0
- package/dist/server/index.d.ts +22 -0
- package/dist/server/index.js +24 -0
- package/dist/server/messages.d.ts +45 -0
- package/dist/server/messages.js +35 -0
- package/dist/server/server-group-model.d.ts +104 -0
- package/dist/server/server-group-model.js +272 -0
- package/dist/server/server-pivot.d.ts +46 -0
- package/dist/server/server-pivot.js +57 -0
- package/dist/server/server-row-model.d.ts +547 -0
- package/dist/server/server-row-model.js +1551 -0
- package/dist/server/server-selection.d.ts +99 -0
- package/dist/server/server-selection.js +215 -0
- package/dist/server/svgrid-adapter.d.ts +155 -0
- package/dist/server/svgrid-adapter.js +202 -0
- package/dist/sheet/ast.d.ts +45 -1
- package/dist/sheet/ast.js +14 -0
- package/dist/sheet/auto-filter.d.ts +58 -4
- package/dist/sheet/auto-filter.js +159 -11
- package/dist/sheet/autocomplete.d.ts +9 -0
- package/dist/sheet/autocomplete.js +90 -4
- package/dist/sheet/cell-images.d.ts +39 -0
- package/dist/sheet/cell-images.js +90 -0
- package/dist/sheet/cell-types.d.ts +58 -0
- package/dist/sheet/cell-types.js +96 -0
- package/dist/sheet/coerce.d.ts +40 -0
- package/dist/sheet/coerce.js +170 -3
- package/dist/sheet/comments.d.ts +43 -9
- package/dist/sheet/comments.js +85 -27
- package/dist/sheet/conditional-formats.d.ts +29 -6
- package/dist/sheet/conditional-formats.js +37 -12
- package/dist/sheet/csv.d.ts +32 -0
- package/dist/sheet/csv.js +158 -0
- package/dist/sheet/culture.d.ts +87 -0
- package/dist/sheet/culture.js +247 -0
- package/dist/sheet/delta.d.ts +102 -0
- package/dist/sheet/delta.js +203 -0
- package/dist/sheet/deps.d.ts +45 -1
- package/dist/sheet/deps.js +77 -2
- package/dist/sheet/document.d.ts +106 -4
- package/dist/sheet/document.js +204 -3
- package/dist/sheet/engine.d.ts +67 -0
- package/dist/sheet/engine.js +19 -0
- package/dist/sheet/entry.d.ts +8 -4
- package/dist/sheet/entry.js +171 -21
- package/dist/sheet/error-check.d.ts +49 -0
- package/dist/sheet/error-check.js +83 -0
- package/dist/sheet/evaluate-steps.d.ts +48 -0
- package/dist/sheet/evaluate-steps.js +196 -0
- package/dist/sheet/evaluate.d.ts +67 -0
- package/dist/sheet/evaluate.js +725 -13
- package/dist/sheet/filter-files.d.ts +37 -0
- package/dist/sheet/filter-files.js +465 -0
- package/dist/sheet/format-store.d.ts +15 -0
- package/dist/sheet/format-store.js +11 -1
- package/dist/sheet/from-rows.d.ts +14 -0
- package/dist/sheet/from-rows.js +50 -0
- package/dist/sheet/function-catalog.d.ts +1 -1
- package/dist/sheet/function-catalog.js +296 -5
- package/dist/sheet/functions.js +363 -91
- package/dist/sheet/index.d.ts +35 -8
- package/dist/sheet/index.js +35 -8
- package/dist/sheet/links.d.ts +57 -0
- package/dist/sheet/links.js +174 -0
- package/dist/sheet/messages.d.ts +778 -0
- package/dist/sheet/messages.js +806 -0
- package/dist/sheet/number-format.d.ts +52 -5
- package/dist/sheet/number-format.js +465 -68
- package/dist/sheet/objects.d.ts +106 -0
- package/dist/sheet/objects.js +149 -0
- package/dist/sheet/ods-document.d.ts +44 -0
- package/dist/sheet/ods-document.js +1275 -0
- package/dist/sheet/open-file.d.ts +26 -0
- package/dist/sheet/open-file.js +56 -0
- package/dist/sheet/outline.d.ts +82 -0
- package/dist/sheet/outline.js +228 -0
- package/dist/sheet/packs/array.d.ts +16 -0
- package/dist/sheet/packs/array.js +172 -0
- package/dist/sheet/packs/database.d.ts +42 -0
- package/dist/sheet/packs/database.js +142 -0
- package/dist/sheet/packs/engineering.d.ts +2 -0
- package/dist/sheet/packs/engineering.js +296 -0
- package/dist/sheet/packs/financial.d.ts +5 -0
- package/dist/sheet/packs/financial.js +220 -0
- package/dist/sheet/packs/math-stats.d.ts +2 -0
- package/dist/sheet/packs/math-stats.js +372 -0
- package/dist/sheet/packs/special.d.ts +44 -0
- package/dist/sheet/packs/special.js +187 -0
- package/dist/sheet/packs/stats-dist.d.ts +2 -0
- package/dist/sheet/packs/stats-dist.js +785 -0
- package/dist/sheet/packs/text-date.d.ts +2 -0
- package/dist/sheet/packs/text-date.js +332 -0
- package/dist/sheet/packs/trig.d.ts +2 -0
- package/dist/sheet/packs/trig.js +73 -0
- package/dist/sheet/page-setup.d.ts +73 -0
- package/dist/sheet/page-setup.js +73 -0
- package/dist/sheet/parse.js +42 -10
- package/dist/sheet/paste-special.d.ts +22 -0
- package/dist/sheet/paste-special.js +14 -5
- package/dist/sheet/pivot-range.d.ts +160 -0
- package/dist/sheet/pivot-range.js +333 -0
- package/dist/sheet/presence.d.ts +73 -0
- package/dist/sheet/presence.js +79 -0
- package/dist/sheet/print.d.ts +104 -0
- package/dist/sheet/print.js +169 -0
- package/dist/sheet/protection.d.ts +55 -1
- package/dist/sheet/protection.js +55 -2
- package/dist/sheet/rects.d.ts +1 -1
- package/dist/sheet/refs.d.ts +48 -0
- package/dist/sheet/refs.js +155 -8
- package/dist/sheet/ribbon-icons.d.ts +1 -1
- package/dist/sheet/ribbon-icons.js +186 -0
- package/dist/sheet/ribbon.d.ts +1 -1
- package/dist/sheet/ribbon.js +324 -23
- package/dist/sheet/sheet-kinds.d.ts +74 -0
- package/dist/sheet/sheet-kinds.js +91 -0
- package/dist/sheet/shortcuts.d.ts +13 -3
- package/dist/sheet/shortcuts.js +63 -12
- package/dist/sheet/sort.d.ts +51 -0
- package/dist/sheet/sort.js +107 -0
- package/dist/sheet/sparklines.d.ts +111 -0
- package/dist/sheet/sparklines.js +204 -0
- package/dist/sheet/table-styles.d.ts +74 -0
- package/dist/sheet/table-styles.js +152 -0
- package/dist/sheet/tables.d.ts +19 -0
- package/dist/sheet/tables.js +31 -0
- package/dist/sheet/tokenize.d.ts +18 -1
- package/dist/sheet/tokenize.js +60 -9
- package/dist/sheet/validation.d.ts +16 -0
- package/dist/sheet/validation.js +37 -0
- package/dist/sheet/workbook.d.ts +148 -3
- package/dist/sheet/workbook.js +742 -31
- package/dist/sheet/xls-cfb.d.ts +32 -0
- package/dist/sheet/xls-cfb.js +290 -0
- package/dist/sheet/xls-document.d.ts +10 -0
- package/dist/sheet/xls-document.js +1243 -0
- package/dist/sheet/xls-formula.d.ts +40 -0
- package/dist/sheet/xls-formula.js +569 -0
- package/dist/sheet/xlsx-document.d.ts +108 -0
- package/dist/sheet/xlsx-document.js +1849 -0
- package/dist/sheet/xlsx-drawing.d.ts +99 -0
- package/dist/sheet/xlsx-drawing.js +420 -0
- package/dist/sheet-enable.js +6 -7
- package/dist/sheet-text.d.ts +7 -0
- package/dist/sheet-text.js +29 -0
- package/dist/sources/auth-supabase.js +2 -2
- package/dist/sources/realtime-supabase.js +2 -2
- package/dist/sources/rest.d.ts +6 -2
- package/dist/sources/rest.js +42 -7
- package/dist/sources/supabase.d.ts +10 -1
- package/dist/sources/supabase.js +73 -40
- package/dist/studio/emit-project.js +14 -1
- package/dist/studio/index.js +1 -1
- package/dist/studio/project.d.ts +11 -2
- package/dist/studio/project.js +5 -2
- package/dist/studio/ui-components.generated.js +107 -2
- package/dist/sveltekit/in-memory.d.ts +2 -2
- package/dist/sveltekit/in-memory.js +125 -4
- package/dist/sveltekit/index.d.ts +1 -1
- package/dist/sveltekit/index.js +1 -1
- package/dist/sveltekit/query-plan.d.ts +50 -4
- package/dist/sveltekit/query-plan.js +25 -3
- package/dist/sveltekit/sql-source.d.ts +6 -0
- package/dist/sveltekit/sql-source.js +34 -6
- package/dist/sveltekit/sql.d.ts +31 -0
- package/dist/sveltekit/sql.js +135 -25
- package/dist/sveltekit/transport.d.ts +10 -2
- package/dist/sveltekit/transport.js +30 -6
- package/dist/upgrade-prompt.d.ts +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/watermark.js +35 -2
- package/dist/wc/GridMenus-By9vPuq0.js +1088 -0
- package/dist/wc/SvChartMenu-CQNrQGT2.js +308 -0
- package/dist/wc/SvDateRangeInput-DR6m7bSM.js +199 -0
- package/dist/wc/SvDateTimePicker-D-vRnaMo.js +771 -0
- package/dist/wc/SvField-B_kurx_P.js +149 -0
- package/dist/wc/SvGrid.helpers-BdVDtjUm.js +135 -0
- package/dist/wc/SvGridCellEditor-Bnf1Kr5C.js +560 -0
- package/dist/wc/SvGridChart-BqUJh2pG.js +3572 -0
- package/dist/wc/SvGridChartBuilder-CGycC5cn.js +988 -0
- package/dist/wc/SvGridChartPanel-CRnizsHD.js +1746 -0
- package/dist/wc/SvGridChartView-D1pa_xMy.js +47 -0
- package/dist/wc/SvGridDropdown-45WsObLL.js +259 -0
- package/dist/wc/SvModal-DrfOYYyX.js +189 -0
- package/dist/wc/cell-formatting-CBfgahv1.js +86 -0
- package/dist/wc/chart-6OXlybVT.js +4341 -0
- package/dist/wc/chart-export-CeSADGfi.js +135 -0
- package/dist/wc/chart-export-pdf-BGko7MER.js +104 -0
- package/dist/wc/chart-summary-2m3ia1-n.js +137 -0
- package/dist/wc/chart-trend-CQOQtTMl.js +50 -0
- package/dist/wc/chart-validate-CD6ptiwg.js +180 -0
- package/dist/wc/client-DQIzjDBG.js +3064 -0
- package/dist/wc/column-resize-DUPgynvc.js +101 -0
- package/dist/wc/date-format-DS-z_h9v.js +1130 -0
- package/dist/wc/dismissable-CQ6XFXsk.js +45 -0
- package/dist/wc/editor-contract-peBDxir_.js +22 -0
- package/dist/wc/editor-registry-DxGKqcvx.js +24 -0
- package/dist/wc/focus-trap-Dbb2tJxb.js +65 -0
- package/dist/wc/list-option-DrRyivWp.js +82 -0
- package/dist/wc/popover-WsGlvtQv.js +69 -0
- package/dist/wc/positioning-BiKLfu1E.js +112 -0
- package/dist/wc/react.d.ts +41 -0
- package/dist/wc/react.js +90 -0
- package/dist/wc/rolldown-runtime-Dy4uBu1J.js +11 -0
- package/dist/wc/row-drag-touch-CycRR65n.js +93 -0
- package/dist/wc/row-resize-limG_N1z.js +111 -0
- package/dist/wc/sv-sheet-element.d.ts +97 -0
- package/dist/wc/sv-sheet-element.js +44213 -0
- package/dist/wc/svelte-virtualizer.svelte-CXtQy2TQ.js +530 -0
- package/dist/wc/validate-BqDthkNr.js +88 -0
- package/dist/wc/vue.d.ts +31 -0
- package/dist/wc/vue.js +75 -0
- package/package.json +30 -5
- package/dist/designer/assets/GridMenus-BlsvL_aU.js +0 -8
- package/dist/designer/assets/SvGridCellEditor-C7uQXYds.js +0 -4
- package/dist/designer/assets/SvGridChartBuilder-kvyIU_Rl.js +0 -1
- package/dist/designer/assets/SvGridDropdown-Bb3HH2Aj.js +0 -1
- package/dist/designer/assets/cell-formatting-wr5wm7US.js +0 -1
- package/dist/designer/assets/index-CMFgAoVV.css +0 -1
- package/dist/designer/assets/row-resize-DLktcqUK.js +0 -1
- package/dist/designer/assets/src-EdI3Xqds.js +0 -58
- package/dist/designer/assets/validate-h-aslRV9.js +0 -1
package/dist/sheet/evaluate.js
CHANGED
|
@@ -16,7 +16,25 @@
|
|
|
16
16
|
import { FormulaError, isError, err, } from './ast.js';
|
|
17
17
|
import { toNumber, toBool, toText, looseEquals, compare } from './coerce.js';
|
|
18
18
|
import { withCustomFunctions } from './functions.js';
|
|
19
|
+
import { ARRAY_FUNCTIONS } from './packs/array.js';
|
|
20
|
+
import { colToLetters } from './address.js';
|
|
21
|
+
import { parseFormula } from './parse.js';
|
|
19
22
|
import { resolveTableRange, columnIndexOf, } from './tables.js';
|
|
23
|
+
const isLambda = (v) => typeof v === 'object' && v !== null && !Array.isArray(v) && 'lambda' in v;
|
|
24
|
+
const isGrid = (v) => Array.isArray(v);
|
|
25
|
+
/** The names a lambda helper takes its function in. */
|
|
26
|
+
const LAMBDA_HELPERS = new Set(['MAP', 'BYROW', 'BYCOL', 'REDUCE', 'SCAN', 'MAKEARRAY']);
|
|
27
|
+
/** A local binding, by name, case-insensitively as Excel reads a name. */
|
|
28
|
+
function localOf(ctx, name) {
|
|
29
|
+
return ctx.locals?.get(name.toUpperCase());
|
|
30
|
+
}
|
|
31
|
+
/** The same context with more names bound over it. */
|
|
32
|
+
function withLocals(ctx, added) {
|
|
33
|
+
const next = new Map(ctx.locals ?? []);
|
|
34
|
+
for (const [name, value] of added)
|
|
35
|
+
next.set(name.toUpperCase(), value);
|
|
36
|
+
return { ...ctx, locals: next };
|
|
37
|
+
}
|
|
20
38
|
function rangeGrid(from, to, ctx) {
|
|
21
39
|
const sheet = from.sheet ?? to.sheet;
|
|
22
40
|
const r1 = Math.min(from.row ?? 0, to.row ?? from.row ?? 0);
|
|
@@ -56,12 +74,159 @@ function binary(op, l, r) {
|
|
|
56
74
|
}
|
|
57
75
|
/** Functions handled before the table, because their arguments must not all
|
|
58
76
|
* be evaluated up front. */
|
|
59
|
-
const SHORT_CIRCUIT = new Set(['IF', 'IFS', 'IFERROR', 'IFNA', 'SWITCH']);
|
|
77
|
+
const SHORT_CIRCUIT = new Set(['IF', 'IFS', 'IFERROR', 'IFNA', 'SWITCH', 'ISERROR', 'ISERR', 'ISNA']);
|
|
78
|
+
/** Functions that PRODUCE a reference rather than a value. In scalar position
|
|
79
|
+
* they read as their top-left cell, as a range does; as an argument they
|
|
80
|
+
* expand to the range, so =SUM(OFFSET(A1,0,0,3,1)) adds three cells. */
|
|
81
|
+
const REFERENCE_FUNCTIONS = new Set(['OFFSET', 'INDIRECT']);
|
|
82
|
+
/**
|
|
83
|
+
* The rectangle a node stands for: a cell, a range (open-ended ones closed
|
|
84
|
+
* at the last used row), a name that refers to one, or a reference
|
|
85
|
+
* function's result. Null for anything that is a plain value.
|
|
86
|
+
*/
|
|
87
|
+
function referenceOf(node, ctx) {
|
|
88
|
+
switch (node.k) {
|
|
89
|
+
case 'ref':
|
|
90
|
+
return { sheet: node.ref.sheet, r1: node.ref.row ?? 0, c1: node.ref.col, r2: node.ref.row ?? 0, c2: node.ref.col };
|
|
91
|
+
case 'range': {
|
|
92
|
+
const sheet = node.from.sheet ?? node.to.sheet;
|
|
93
|
+
const r1 = Math.min(node.from.row ?? 0, node.to.row ?? node.from.row ?? 0);
|
|
94
|
+
const r2 = node.to.row === null || node.from.row === null ? ctx.lastRow(sheet) : Math.max(node.from.row, node.to.row);
|
|
95
|
+
return { sheet, r1, c1: Math.min(node.from.col, node.to.col), r2, c2: Math.max(node.from.col, node.to.col) };
|
|
96
|
+
}
|
|
97
|
+
case 'name': {
|
|
98
|
+
const target = nameTarget(node.name, ctx);
|
|
99
|
+
return target ? referenceOf(target, ctx) : null;
|
|
100
|
+
}
|
|
101
|
+
case 'fn':
|
|
102
|
+
return REFERENCE_FUNCTIONS.has(node.name) ? referenceCall(node, ctx) : null;
|
|
103
|
+
case 'table': {
|
|
104
|
+
// A structured reference is a rectangle too, so SUBTOTAL(109,[Qty])
|
|
105
|
+
// in a totals row skips the rows a filter folds, as Excel's does,
|
|
106
|
+
// rather than reading the column's first cell as a scalar.
|
|
107
|
+
const rect = tableRectOf(node, ctx);
|
|
108
|
+
return rect ? { sheet: rect.sheet, r1: rect.firstRow, c1: rect.firstCol, r2: rect.lastRow, c2: rect.lastCol } : null;
|
|
109
|
+
}
|
|
110
|
+
case 'spill':
|
|
111
|
+
return spillRectOf(node, ctx);
|
|
112
|
+
default:
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* The rectangle a spilled-range operator (`A1#`) stands for: the array the
|
|
118
|
+
* anchor currently spills. Null when the cell is not a spill anchor, which
|
|
119
|
+
* the callers turn into #REF!, the way Excel does for `A1#` on a cell that
|
|
120
|
+
* holds no dynamic array.
|
|
121
|
+
*/
|
|
122
|
+
function spillRectOf(node, ctx) {
|
|
123
|
+
const rect = ctx.spillRect?.(node.ref.sheet, node.ref.row ?? 0, node.ref.col);
|
|
124
|
+
return rect ? { sheet: node.ref.sheet, r1: rect[0], c1: rect[1], r2: rect[2], c2: rect[3] } : null;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Every value a 3D reference reads: the cell or rectangle on each sheet in
|
|
128
|
+
* the tab range, in sheet order. Null when a sheet name is unknown, which the
|
|
129
|
+
* callers turn into #REF!. Aggregates take the flat list, so a nested
|
|
130
|
+
* rectangle spans sheet by sheet.
|
|
131
|
+
*/
|
|
132
|
+
function values3d(node, ctx) {
|
|
133
|
+
const sheets = ctx.sheetsBetween?.(node.sheetFrom, node.sheetTo);
|
|
134
|
+
if (!sheets)
|
|
135
|
+
return null;
|
|
136
|
+
const r1 = Math.min(node.from.row ?? 0, node.to.row ?? 0);
|
|
137
|
+
const r2 = Math.max(node.from.row ?? 0, node.to.row ?? 0);
|
|
138
|
+
const c1 = Math.min(node.from.col, node.to.col);
|
|
139
|
+
const c2 = Math.max(node.from.col, node.to.col);
|
|
140
|
+
const out = [];
|
|
141
|
+
for (const s of sheets) {
|
|
142
|
+
for (let r = r1; r <= r2; r += 1) {
|
|
143
|
+
for (let c = c1; c <= c2; c += 1)
|
|
144
|
+
out.push(ctx.resolve(s, r, c));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return out;
|
|
148
|
+
}
|
|
149
|
+
function rectGrid(rect, ctx) {
|
|
150
|
+
return rangeGrid({ sheet: rect.sheet, row: rect.r1, col: rect.c1, rowAbs: false, colAbs: false }, { sheet: rect.sheet, row: rect.r2, col: rect.c2, rowAbs: false, colAbs: false }, ctx);
|
|
151
|
+
}
|
|
152
|
+
const scalarArg = (node, ctx) => (node ? evalNode(node, ctx) : '');
|
|
153
|
+
const optNumber = (node, ctx, fallback) => {
|
|
154
|
+
if (!node || node.k === 'empty')
|
|
155
|
+
return fallback;
|
|
156
|
+
const v = evalNode(node, ctx);
|
|
157
|
+
if (isError(v))
|
|
158
|
+
throw new FormulaError(v.error);
|
|
159
|
+
return v === '' ? fallback : toNumber(v);
|
|
160
|
+
};
|
|
161
|
+
/** OFFSET and INDIRECT: the rectangle they name, or a throw for the error. */
|
|
162
|
+
function referenceCall(node, ctx) {
|
|
163
|
+
const { name, args } = node;
|
|
164
|
+
if (name === 'INDIRECT') {
|
|
165
|
+
const text = scalarArg(args[0], ctx);
|
|
166
|
+
if (isError(text))
|
|
167
|
+
throw new FormulaError(text.error);
|
|
168
|
+
const a1 = args[1] && args[1].k !== 'empty' ? toBool(evalNode(args[1], ctx)) : true;
|
|
169
|
+
if (!a1)
|
|
170
|
+
throw new FormulaError('#REF!');
|
|
171
|
+
let parsed;
|
|
172
|
+
try {
|
|
173
|
+
parsed = parseFormula(`=${toText(text).trim()}`);
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
throw new FormulaError('#REF!');
|
|
177
|
+
}
|
|
178
|
+
const rect = parsed.k === 'ref' || parsed.k === 'range' || parsed.k === 'name' ? referenceOf(parsed, ctx) : null;
|
|
179
|
+
if (!rect)
|
|
180
|
+
throw new FormulaError('#REF!');
|
|
181
|
+
return rect;
|
|
182
|
+
}
|
|
183
|
+
// OFFSET(reference, rows, cols, [height], [width])
|
|
184
|
+
const base = args[0] ? referenceOf(args[0], ctx) : null;
|
|
185
|
+
if (!base)
|
|
186
|
+
throw new FormulaError('#VALUE!');
|
|
187
|
+
const rows = Math.trunc(optNumber(args[1], ctx, 0));
|
|
188
|
+
const cols = Math.trunc(optNumber(args[2], ctx, 0));
|
|
189
|
+
const height = Math.trunc(optNumber(args[3], ctx, base.r2 - base.r1 + 1));
|
|
190
|
+
const width = Math.trunc(optNumber(args[4], ctx, base.c2 - base.c1 + 1));
|
|
191
|
+
if (height <= 0 || width <= 0)
|
|
192
|
+
throw new FormulaError('#REF!');
|
|
193
|
+
const r1 = base.r1 + rows;
|
|
194
|
+
const c1 = base.c1 + cols;
|
|
195
|
+
if (r1 < 0 || c1 < 0)
|
|
196
|
+
throw new FormulaError('#REF!');
|
|
197
|
+
return { sheet: base.sheet, r1, c1, r2: r1 + height - 1, c2: c1 + width - 1 };
|
|
198
|
+
}
|
|
199
|
+
/** ROW, COLUMN and ADDRESS: about a reference, not its value. */
|
|
200
|
+
function positionCall(node, ctx) {
|
|
201
|
+
const { name, args } = node;
|
|
202
|
+
if (name === 'ADDRESS') {
|
|
203
|
+
const row = Math.trunc(toNumber(scalarArg(args[0], ctx)));
|
|
204
|
+
const col = Math.trunc(toNumber(scalarArg(args[1], ctx)));
|
|
205
|
+
const abs = Math.trunc(optNumber(args[2], ctx, 1));
|
|
206
|
+
if (row < 1 || col < 1 || abs < 1 || abs > 4)
|
|
207
|
+
return err('#VALUE!');
|
|
208
|
+
const sheet = args[4] && args[4].k !== 'empty' ? toText(scalarArg(args[4], ctx)) : '';
|
|
209
|
+
const rowText = `${abs === 1 || abs === 2 ? '$' : ''}${row}`;
|
|
210
|
+
const colText = `${abs === 1 || abs === 3 ? '$' : ''}${colToLetters(col - 1)}`;
|
|
211
|
+
const prefix = sheet ? (/^[A-Za-z_][A-Za-z0-9_]*$/.test(sheet) ? `${sheet}!` : `'${sheet.replace(/'/g, "''")}'!`) : '';
|
|
212
|
+
return `${prefix}${colText}${rowText}`;
|
|
213
|
+
}
|
|
214
|
+
const target = args[0] && args[0].k !== 'empty' ? referenceOf(args[0], ctx) : null;
|
|
215
|
+
if (!target) {
|
|
216
|
+
const here = ctx.currentCell;
|
|
217
|
+
if (!here)
|
|
218
|
+
return err('#VALUE!');
|
|
219
|
+
return name === 'ROW' ? here.row + 1 : here.col + 1;
|
|
220
|
+
}
|
|
221
|
+
return name === 'ROW' ? target.r1 + 1 : target.c1 + 1;
|
|
222
|
+
}
|
|
60
223
|
function evalNode(node, ctx) {
|
|
61
224
|
switch (node.k) {
|
|
62
225
|
case 'num': return node.v;
|
|
63
226
|
case 'str': return node.v;
|
|
64
227
|
case 'bool': return node.v;
|
|
228
|
+
case 'err': return err(node.v);
|
|
229
|
+
case 'empty': return '';
|
|
65
230
|
case 'ref':
|
|
66
231
|
return ctx.resolve(node.ref.sheet, node.ref.row ?? 0, node.ref.col);
|
|
67
232
|
case 'range': {
|
|
@@ -70,7 +235,33 @@ function evalNode(node, ctx) {
|
|
|
70
235
|
const grid = rangeGrid(node.from, node.to, ctx);
|
|
71
236
|
return grid[0]?.[0] ?? '';
|
|
72
237
|
}
|
|
238
|
+
case 'spill': {
|
|
239
|
+
// The spilled range in scalar position is its top-left, the anchor's
|
|
240
|
+
// own value; a cell that anchors no array is #REF!.
|
|
241
|
+
const rect = spillRectOf(node, ctx);
|
|
242
|
+
return rect ? ctx.resolve(rect.sheet, rect.r1, rect.c1) : err('#REF!');
|
|
243
|
+
}
|
|
244
|
+
case 'ref3d': {
|
|
245
|
+
// In scalar position a 3D reference reads the cell on the first sheet
|
|
246
|
+
// of the range, the way an ordinary range collapses to its top-left.
|
|
247
|
+
const vals = values3d(node, ctx);
|
|
248
|
+
if (!vals)
|
|
249
|
+
return err('#REF!');
|
|
250
|
+
return vals[0] ?? '';
|
|
251
|
+
}
|
|
73
252
|
case 'name': {
|
|
253
|
+
// A name bound inside the formula wins over the workbook's own, which
|
|
254
|
+
// is what makes `=LET(x, 2, x * 3)` mean 6 on a sheet that has an `x`.
|
|
255
|
+
const local = localOf(ctx, node.name);
|
|
256
|
+
if (local !== undefined) {
|
|
257
|
+
// A lambda that is never called is #CALC!, as Excel shows it; a
|
|
258
|
+
// grid in scalar position reads as its top-left cell.
|
|
259
|
+
if (isLambda(local))
|
|
260
|
+
return err('#CALC!');
|
|
261
|
+
if (isGrid(local))
|
|
262
|
+
return local[0]?.[0] ?? '';
|
|
263
|
+
return local;
|
|
264
|
+
}
|
|
74
265
|
const target = nameTarget(node.name, ctx);
|
|
75
266
|
if (target)
|
|
76
267
|
return evalNode(target, ctx);
|
|
@@ -109,7 +300,7 @@ function evalNode(node, ctx) {
|
|
|
109
300
|
* naming a column that was renamed is an ordinary thing to find in a sheet,
|
|
110
301
|
* and one broken total should not take the rest of the workbook with it.
|
|
111
302
|
*/
|
|
112
|
-
function tableRectOf(node, ctx) {
|
|
303
|
+
export function tableRectOf(node, ctx) {
|
|
113
304
|
const here = ctx.currentCell;
|
|
114
305
|
const table = node.table
|
|
115
306
|
? ctx.findTable?.(node.table)
|
|
@@ -175,6 +366,64 @@ function nameTarget(name, ctx) {
|
|
|
175
366
|
return node;
|
|
176
367
|
}
|
|
177
368
|
/** Functions whose arguments must not all be evaluated up front. */
|
|
369
|
+
/**
|
|
370
|
+
* `SUBTOTAL(code, ref1, ...)`: the aggregate Excel's AutoFilter is built on.
|
|
371
|
+
*
|
|
372
|
+
* The code names the function - 1 AVERAGE, 2 COUNT, 3 COUNTA, 4 MAX, 5 MIN,
|
|
373
|
+
* 6 PRODUCT, 7 STDEV, 8 STDEVP, 9 SUM, 10 VAR, 11 VARP - and a hundred more
|
|
374
|
+
* means "and leave out the rows hidden by hand as well". A filtered-out row
|
|
375
|
+
* is left out either way, which is what makes a total under a filtered list
|
|
376
|
+
* follow the filter, and why a table's totals row is written with this
|
|
377
|
+
* rather than with SUM. A cell holding a SUBTOTAL of its own is skipped, so
|
|
378
|
+
* a grand total over subtotals counts each row once.
|
|
379
|
+
*/
|
|
380
|
+
const SUBTOTAL_FUNCTIONS = {
|
|
381
|
+
1: 'AVERAGE', 2: 'COUNT', 3: 'COUNTA', 4: 'MAX', 5: 'MIN', 6: 'PRODUCT',
|
|
382
|
+
7: 'STDEV', 8: 'STDEVP', 9: 'SUM', 10: 'VAR', 11: 'VARP',
|
|
383
|
+
};
|
|
384
|
+
function subtotalCall(node, ctx) {
|
|
385
|
+
const first = node.args[0];
|
|
386
|
+
if (!first)
|
|
387
|
+
return err('#VALUE!');
|
|
388
|
+
const codeValue = evalNode(first, ctx);
|
|
389
|
+
if (isError(codeValue))
|
|
390
|
+
return codeValue;
|
|
391
|
+
const code = Math.trunc(toNumber(codeValue));
|
|
392
|
+
const ignoreHandHidden = code > 100;
|
|
393
|
+
const fnName = SUBTOTAL_FUNCTIONS[ignoreHandHidden ? code - 100 : code];
|
|
394
|
+
if (!fnName)
|
|
395
|
+
return err('#VALUE!');
|
|
396
|
+
const table = ctx.functions ?? withCustomFunctions(undefined);
|
|
397
|
+
const fn = table[fnName];
|
|
398
|
+
if (!fn)
|
|
399
|
+
return err('#NAME?');
|
|
400
|
+
const perArg = [];
|
|
401
|
+
for (const arg of node.args.slice(1)) {
|
|
402
|
+
const rect = referenceOf(arg, ctx);
|
|
403
|
+
if (!rect) {
|
|
404
|
+
// A plain value or an expression: nothing is hidden about it.
|
|
405
|
+
perArg.push([evalNode(arg, ctx)]);
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
const values = [];
|
|
409
|
+
for (let r = rect.r1; r <= rect.r2; r += 1) {
|
|
410
|
+
const how = ctx.hiddenRow?.(rect.sheet, r) ?? null;
|
|
411
|
+
if (how === 'filter' || (how === 'hand' && ignoreHandHidden))
|
|
412
|
+
continue;
|
|
413
|
+
for (let c = rect.c1; c <= rect.c2; c += 1) {
|
|
414
|
+
if (ctx.isSubtotal?.(rect.sheet, r, c))
|
|
415
|
+
continue;
|
|
416
|
+
values.push(ctx.resolve(rect.sheet, r, c));
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
perArg.push(values);
|
|
420
|
+
}
|
|
421
|
+
const flat = perArg.flat();
|
|
422
|
+
for (const v of flat)
|
|
423
|
+
if (isError(v))
|
|
424
|
+
return v;
|
|
425
|
+
return fn({ flat, args: perArg, grids: perArg.map(() => null) });
|
|
426
|
+
}
|
|
178
427
|
function evalCall(node, ctx) {
|
|
179
428
|
const { name, args } = node;
|
|
180
429
|
// Every short-circuiting function below indexes its arguments directly.
|
|
@@ -182,6 +431,26 @@ function evalCall(node, ctx) {
|
|
|
182
431
|
// TypeError escapes the boundary this module promises never to throw past.
|
|
183
432
|
if (SHORT_CIRCUIT.has(name) && args.length === 0)
|
|
184
433
|
return err('#VALUE!');
|
|
434
|
+
// LET, LAMBDA and the helpers that take one see the AST rather than
|
|
435
|
+
// evaluated arguments: a lambda IS its body, and a name bound by LET has
|
|
436
|
+
// to exist before the expression that reads it is evaluated.
|
|
437
|
+
if (name === 'LET')
|
|
438
|
+
return evalLet(args, ctx);
|
|
439
|
+
// The call the parser builds for `LAMBDA(x, x * 2)(5)`: the callee first,
|
|
440
|
+
// then the arguments it is called with.
|
|
441
|
+
if (name === '(')
|
|
442
|
+
return scalarOf(bindingOf(node, ctx));
|
|
443
|
+
if (name === 'LAMBDA')
|
|
444
|
+
return err('#CALC!');
|
|
445
|
+
if (LAMBDA_HELPERS.has(name)) {
|
|
446
|
+
const grid = lambdaCall(node, ctx);
|
|
447
|
+
return Array.isArray(grid) ? grid[0]?.[0] ?? '' : grid;
|
|
448
|
+
}
|
|
449
|
+
// `=LET(double, LAMBDA(x, x * 2), double(21))`: a call on a name the
|
|
450
|
+
// formula bound rather than on a function the library ships.
|
|
451
|
+
const bound = localOf(ctx, name);
|
|
452
|
+
if (isLambda(bound))
|
|
453
|
+
return applyLambda(bound, args.map((a) => bindingOf(a, ctx)), ctx);
|
|
185
454
|
if (name === 'IF') {
|
|
186
455
|
const cond = evalNode(args[0], ctx);
|
|
187
456
|
if (isError(cond))
|
|
@@ -191,6 +460,26 @@ function evalCall(node, ctx) {
|
|
|
191
460
|
return toBool(cond);
|
|
192
461
|
return evalNode(branch, ctx);
|
|
193
462
|
}
|
|
463
|
+
if (name === 'ISERROR' || name === 'ISERR' || name === 'ISNA') {
|
|
464
|
+
let code = null;
|
|
465
|
+
try {
|
|
466
|
+
const v = evalNode(args[0], ctx);
|
|
467
|
+
if (isError(v))
|
|
468
|
+
code = v.error;
|
|
469
|
+
}
|
|
470
|
+
catch (e) {
|
|
471
|
+
if (!(e instanceof FormulaError))
|
|
472
|
+
throw e;
|
|
473
|
+
code = e.code;
|
|
474
|
+
}
|
|
475
|
+
if (code === null)
|
|
476
|
+
return false;
|
|
477
|
+
if (name === 'ISNA')
|
|
478
|
+
return code === '#N/A';
|
|
479
|
+
if (name === 'ISERR')
|
|
480
|
+
return code !== '#N/A';
|
|
481
|
+
return true;
|
|
482
|
+
}
|
|
194
483
|
if (name === 'IFERROR' || name === 'IFNA') {
|
|
195
484
|
try {
|
|
196
485
|
const v = evalNode(args[0], ctx);
|
|
@@ -232,14 +521,335 @@ function evalCall(node, ctx) {
|
|
|
232
521
|
// A trailing odd argument is the default.
|
|
233
522
|
return i < args.length ? evalNode(args[i], ctx) : err('#N/A');
|
|
234
523
|
}
|
|
524
|
+
if (name === 'SUBTOTAL')
|
|
525
|
+
return subtotalCall(node, ctx);
|
|
526
|
+
if (name === 'ROW' || name === 'COLUMN' || name === 'ADDRESS')
|
|
527
|
+
return positionCall(node, ctx);
|
|
528
|
+
if (REFERENCE_FUNCTIONS.has(name)) {
|
|
529
|
+
// In scalar position a reference collapses to its top-left cell.
|
|
530
|
+
const rect = referenceCall(node, ctx);
|
|
531
|
+
return ctx.resolve(rect.sheet, rect.r1, rect.c1);
|
|
532
|
+
}
|
|
533
|
+
if (ARRAY_FUNCTIONS[name]) {
|
|
534
|
+
// In scalar position a grid reads as its top-left cell; the workbook
|
|
535
|
+
// asks `evaluateSpill` for the whole of it.
|
|
536
|
+
const grid = arrayCall(node, ctx);
|
|
537
|
+
return Array.isArray(grid) ? grid[0]?.[0] ?? '' : grid;
|
|
538
|
+
}
|
|
235
539
|
const table = ctx.functions ?? withCustomFunctions(undefined);
|
|
236
540
|
const fn = table[name];
|
|
237
541
|
if (!fn)
|
|
238
542
|
return err('#NAME?');
|
|
239
|
-
|
|
543
|
+
const { perArg, grids } = collectArgs(args, ctx);
|
|
544
|
+
const flat = perArg.flat();
|
|
545
|
+
// An error anywhere in the arguments propagates, except for the counting
|
|
546
|
+
// functions, which Excel lets see errors in their range, and TYPE, whose
|
|
547
|
+
// whole job is to report that an error IS one (it answers 16).
|
|
548
|
+
if (name !== 'COUNTA' && name !== 'COUNTBLANK' && name !== 'TYPE') {
|
|
549
|
+
for (const v of flat)
|
|
550
|
+
if (isError(v))
|
|
551
|
+
return v;
|
|
552
|
+
}
|
|
553
|
+
const payload = { flat, args: perArg, grids };
|
|
554
|
+
return fn(payload);
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* `LET(name1, value1, [name2, value2, ...], calculation)`.
|
|
558
|
+
*
|
|
559
|
+
* Each value is evaluated in the scope built so far, so a later binding can
|
|
560
|
+
* read an earlier one, and the calculation sees them all. A binding holds a
|
|
561
|
+
* GRID where its expression is one, which is what lets `LET(r, A1:A9,
|
|
562
|
+
* SUM(r))` add the range rather than its first cell.
|
|
563
|
+
*/
|
|
564
|
+
function evalLet(args, ctx) {
|
|
565
|
+
if (args.length < 3 || args.length % 2 === 0)
|
|
566
|
+
return err('#VALUE!');
|
|
567
|
+
let scope = ctx;
|
|
568
|
+
for (let i = 0; i + 1 < args.length - 1; i += 2) {
|
|
569
|
+
const nameNode = args[i];
|
|
570
|
+
if (nameNode.k !== 'name')
|
|
571
|
+
return err('#VALUE!');
|
|
572
|
+
scope = withLocals(scope, [[nameNode.name, bindingOf(args[i + 1], scope)]]);
|
|
573
|
+
}
|
|
574
|
+
const result = bindingOf(args[args.length - 1], scope);
|
|
575
|
+
if (isLambda(result))
|
|
576
|
+
return err('#CALC!');
|
|
577
|
+
if (isGrid(result))
|
|
578
|
+
return result[0]?.[0] ?? '';
|
|
579
|
+
return result;
|
|
580
|
+
}
|
|
581
|
+
/** The grid a LET's calculation stands for, or null when it is one value. */
|
|
582
|
+
function letGrid(args, ctx) {
|
|
583
|
+
if (args.length < 3 || args.length % 2 === 0)
|
|
584
|
+
return null;
|
|
585
|
+
let scope = ctx;
|
|
586
|
+
for (let i = 0; i + 1 < args.length - 1; i += 2) {
|
|
587
|
+
const nameNode = args[i];
|
|
588
|
+
if (nameNode.k !== 'name')
|
|
589
|
+
return null;
|
|
590
|
+
scope = withLocals(scope, [[nameNode.name, bindingOf(args[i + 1], scope)]]);
|
|
591
|
+
}
|
|
592
|
+
const result = bindingOf(args[args.length - 1], scope);
|
|
593
|
+
return isGrid(result) ? result : null;
|
|
594
|
+
}
|
|
595
|
+
/** What a name is bound to: a lambda, a grid, or a plain value. */
|
|
596
|
+
function bindingOf(node, ctx) {
|
|
597
|
+
if (node.k === 'fn' && node.name === 'LAMBDA')
|
|
598
|
+
return lambdaOf(node, ctx);
|
|
599
|
+
if (node.k === 'fn' && node.name === '(') {
|
|
600
|
+
const fn = lambdaArg(node.args[0], ctx);
|
|
601
|
+
if (!fn)
|
|
602
|
+
return err('#VALUE!');
|
|
603
|
+
return callLambda(fn, node.args.slice(1).map((a) => bindingOf(a, ctx)), ctx);
|
|
604
|
+
}
|
|
605
|
+
if (node.k === 'fn') {
|
|
606
|
+
const bound = localOf(ctx, node.name);
|
|
607
|
+
if (isLambda(bound))
|
|
608
|
+
return callLambda(bound, node.args.map((a) => bindingOf(a, ctx)), ctx);
|
|
609
|
+
}
|
|
610
|
+
if (node.k === 'name') {
|
|
611
|
+
const local = localOf(ctx, node.name);
|
|
612
|
+
if (local !== undefined)
|
|
613
|
+
return local;
|
|
614
|
+
}
|
|
615
|
+
// Arithmetic over ranges is a grid too, cell by cell: `B2:B6 * D2:D6` is
|
|
616
|
+
// what a REDUCE or a BYROW is usually handed.
|
|
617
|
+
if ((node.k === 'binary' || node.k === 'unary') && hasArray(node, ctx))
|
|
618
|
+
return gridOf(node, ctx);
|
|
619
|
+
const grid = hasArray(node, ctx) ? rangeValues(node, ctx) : null;
|
|
620
|
+
if (grid && (grid.length > 1 || (grid[0]?.length ?? 0) > 1))
|
|
621
|
+
return grid;
|
|
622
|
+
return evalNode(node, ctx);
|
|
623
|
+
}
|
|
624
|
+
/** The lambda a `LAMBDA(p1, ..., body)` node stands for, closed over `ctx`. */
|
|
625
|
+
function lambdaOf(node, ctx) {
|
|
626
|
+
const args = node.args;
|
|
627
|
+
if (args.length < 1)
|
|
628
|
+
return err('#VALUE!');
|
|
629
|
+
const params = [];
|
|
630
|
+
for (let i = 0; i < args.length - 1; i += 1) {
|
|
631
|
+
const p = args[i];
|
|
632
|
+
if (p.k !== 'name')
|
|
633
|
+
return err('#VALUE!');
|
|
634
|
+
params.push(p.name);
|
|
635
|
+
}
|
|
636
|
+
return { lambda: true, params, body: args[args.length - 1], scope: ctx.locals };
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Call a lambda, keeping whatever it answers with: a value, a grid, or
|
|
640
|
+
* another lambda, which is what a curried `LAMBDA(x, LAMBDA(y, x + y))`
|
|
641
|
+
* gives back.
|
|
642
|
+
*/
|
|
643
|
+
function callLambda(fn, values, ctx) {
|
|
644
|
+
// Excel is strict about the count: too few is #VALUE!, and so is too many.
|
|
645
|
+
if (values.length !== fn.params.length)
|
|
646
|
+
return err('#VALUE!');
|
|
647
|
+
const scope = withLocals({ ...ctx, locals: fn.scope }, fn.params.map((p, i) => [p, values[i]]));
|
|
648
|
+
return bindingOf(fn.body, scope);
|
|
649
|
+
}
|
|
650
|
+
/** The same, in a place that wants one value. */
|
|
651
|
+
function applyLambda(fn, values, ctx) {
|
|
652
|
+
return scalarOf(callLambda(fn, values, ctx));
|
|
653
|
+
}
|
|
654
|
+
/** A binding where one value is wanted: a lambda is #CALC!, a grid its corner. */
|
|
655
|
+
function scalarOf(binding) {
|
|
656
|
+
if (isLambda(binding))
|
|
657
|
+
return err('#CALC!');
|
|
658
|
+
if (isGrid(binding))
|
|
659
|
+
return binding[0]?.[0] ?? '';
|
|
660
|
+
return binding;
|
|
661
|
+
}
|
|
662
|
+
/** The lambda an argument names: written there, or bound to a name. */
|
|
663
|
+
function lambdaArg(node, ctx) {
|
|
664
|
+
if (!node)
|
|
665
|
+
return null;
|
|
666
|
+
if (node.k === 'fn' && node.name === 'LAMBDA') {
|
|
667
|
+
const made = lambdaOf(node, ctx);
|
|
668
|
+
return isLambda(made) ? made : null;
|
|
669
|
+
}
|
|
670
|
+
if (node.k === 'name') {
|
|
671
|
+
const local = localOf(ctx, node.name);
|
|
672
|
+
if (isLambda(local))
|
|
673
|
+
return local;
|
|
674
|
+
return null;
|
|
675
|
+
}
|
|
676
|
+
// A call that answers with another lambda: `LAMBDA(x, LAMBDA(y, x + y))(2)`.
|
|
677
|
+
if (node.k === 'fn' && (node.name === '(' || isLambda(localOf(ctx, node.name)))) {
|
|
678
|
+
const made = bindingOf(node, ctx);
|
|
679
|
+
return isLambda(made) ? made : null;
|
|
680
|
+
}
|
|
681
|
+
return null;
|
|
682
|
+
}
|
|
683
|
+
/** An argument as a grid, one cell wide where it is a plain value. */
|
|
684
|
+
function gridArgOf(node, ctx) {
|
|
685
|
+
if (!node)
|
|
686
|
+
return [['']];
|
|
687
|
+
const binding = bindingOf(node, ctx);
|
|
688
|
+
if (isGrid(binding))
|
|
689
|
+
return binding;
|
|
690
|
+
if (isLambda(binding))
|
|
691
|
+
return [[err('#CALC!')]];
|
|
692
|
+
return [[binding]];
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Excel's lambda helpers, which is where a lambda earns its keep:
|
|
696
|
+
*
|
|
697
|
+
* MAP(array, ..., fn) every cell through the function
|
|
698
|
+
* BYROW(array, fn) one answer per row, as a column
|
|
699
|
+
* BYCOL(array, fn) one answer per column, as a row
|
|
700
|
+
* REDUCE(init, array, fn) folded to one value, accumulator first
|
|
701
|
+
* SCAN(init, array, fn) the same, keeping every step
|
|
702
|
+
* MAKEARRAY(rows, cols, fn) built from the row and column numbers
|
|
703
|
+
*/
|
|
704
|
+
function lambdaCall(node, ctx) {
|
|
705
|
+
const { name, args } = node;
|
|
706
|
+
const fn = lambdaArg(args[args.length - 1], ctx);
|
|
707
|
+
if (!fn)
|
|
708
|
+
return err('#VALUE!');
|
|
709
|
+
if (name === 'MAKEARRAY') {
|
|
710
|
+
const rows = Math.trunc(toNumber(evalNode(args[0] ?? { k: 'num', v: 0 }, ctx)));
|
|
711
|
+
const cols = Math.trunc(toNumber(evalNode(args[1] ?? { k: 'num', v: 0 }, ctx)));
|
|
712
|
+
if (!(rows > 0) || !(cols > 0))
|
|
713
|
+
return err('#VALUE!');
|
|
714
|
+
if (rows * cols > 1000000)
|
|
715
|
+
return err('#NUM!');
|
|
716
|
+
return Array.from({ length: rows }, (_, r) => Array.from({ length: cols }, (_, c) => applyLambda(fn, [r + 1, c + 1], ctx)));
|
|
717
|
+
}
|
|
718
|
+
if (name === 'MAP') {
|
|
719
|
+
const grids = args.slice(0, -1).map((a) => gridArgOf(a, ctx));
|
|
720
|
+
if (!grids.length)
|
|
721
|
+
return err('#VALUE!');
|
|
722
|
+
const rows = grids[0].length;
|
|
723
|
+
const cols = grids[0][0]?.length ?? 0;
|
|
724
|
+
// Every array has to be the same shape, as Excel asks.
|
|
725
|
+
for (const g of grids)
|
|
726
|
+
if (g.length !== rows || (g[0]?.length ?? 0) !== cols)
|
|
727
|
+
return err('#VALUE!');
|
|
728
|
+
return Array.from({ length: rows }, (_, r) => Array.from({ length: cols }, (_, c) => applyLambda(fn, grids.map((g) => g[r]?.[c] ?? ''), ctx)));
|
|
729
|
+
}
|
|
730
|
+
if (name === 'BYROW' || name === 'BYCOL') {
|
|
731
|
+
const grid = gridArgOf(args[0], ctx);
|
|
732
|
+
if (name === 'BYROW') {
|
|
733
|
+
// Each row goes in as a grid one row tall, and the answers come back
|
|
734
|
+
// as a column, which is the shape Excel spills.
|
|
735
|
+
return grid.map((row) => [applyLambda(fn, [[row]], ctx)]);
|
|
736
|
+
}
|
|
737
|
+
// Each column goes in as a grid one column wide, answers along a row.
|
|
738
|
+
return [transposeGrid(grid).map((col) => applyLambda(fn, [col.map((v) => [v])], ctx))];
|
|
739
|
+
}
|
|
740
|
+
// REDUCE and SCAN: the accumulator, then each cell in reading order.
|
|
741
|
+
const grid = gridArgOf(args[1], ctx);
|
|
742
|
+
let acc = args[0] ? evalNode(args[0], ctx) : '';
|
|
743
|
+
const steps = [];
|
|
744
|
+
for (const row of grid) {
|
|
745
|
+
const line = [];
|
|
746
|
+
for (const cell of row) {
|
|
747
|
+
acc = applyLambda(fn, [acc, cell], ctx);
|
|
748
|
+
line.push(acc);
|
|
749
|
+
}
|
|
750
|
+
steps.push(line);
|
|
751
|
+
}
|
|
752
|
+
return name === 'SCAN' ? steps : acc;
|
|
753
|
+
}
|
|
754
|
+
/** A grid with its rows and columns swapped. */
|
|
755
|
+
function transposeGrid(grid) {
|
|
756
|
+
const cols = grid.reduce((m, row) => Math.max(m, row.length), 0);
|
|
757
|
+
return Array.from({ length: cols }, (_, c) => grid.map((row) => row[c] ?? ''));
|
|
758
|
+
}
|
|
759
|
+
/** Whether a node stands for a grid: a range, an array or reference function, a name to one, or arithmetic over those. */
|
|
760
|
+
function hasArray(node, ctx, depth = 0) {
|
|
761
|
+
switch (node.k) {
|
|
762
|
+
case 'range': return true;
|
|
763
|
+
case 'spill': return true;
|
|
764
|
+
case 'name': {
|
|
765
|
+
if (isGrid(localOf(ctx, node.name)))
|
|
766
|
+
return true;
|
|
767
|
+
const target = depth < 8 ? nameTarget(node.name, ctx) : null;
|
|
768
|
+
return target ? hasArray(target, ctx, depth + 1) : false;
|
|
769
|
+
}
|
|
770
|
+
case 'fn': return REFERENCE_FUNCTIONS.has(node.name) || Boolean(ARRAY_FUNCTIONS[node.name]) || LAMBDA_HELPERS.has(node.name) || node.name === 'LET';
|
|
771
|
+
case 'binary': return hasArray(node.left, ctx, depth) || hasArray(node.right, ctx, depth);
|
|
772
|
+
case 'unary': return hasArray(node.arg, ctx, depth);
|
|
773
|
+
default: return false;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* A node as a grid, with Excel's broadcasting: a side that is one row, one
|
|
778
|
+
* column or one cell stretches to the other's shape, and where both are
|
|
779
|
+
* bigger and differ the cells past the shorter side are #N/A.
|
|
780
|
+
*/
|
|
781
|
+
function gridOf(node, ctx) {
|
|
782
|
+
if (node.k === 'binary') {
|
|
783
|
+
const l = gridOf(node.left, ctx);
|
|
784
|
+
const r = gridOf(node.right, ctx);
|
|
785
|
+
return broadcast(l, r, (a, b) => {
|
|
786
|
+
if (isError(a))
|
|
787
|
+
return a;
|
|
788
|
+
if (isError(b))
|
|
789
|
+
return b;
|
|
790
|
+
try {
|
|
791
|
+
return binary(node.op, a, b);
|
|
792
|
+
}
|
|
793
|
+
catch (e) {
|
|
794
|
+
if (e instanceof FormulaError)
|
|
795
|
+
return err(e.code);
|
|
796
|
+
throw e;
|
|
797
|
+
}
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
if (node.k === 'unary') {
|
|
801
|
+
return gridOf(node.arg, ctx).map((row) => row.map((v) => {
|
|
802
|
+
if (isError(v))
|
|
803
|
+
return v;
|
|
804
|
+
try {
|
|
805
|
+
return node.op === '-' ? -toNumber(v) : node.op === '%' ? toNumber(v) / 100 : toNumber(v);
|
|
806
|
+
}
|
|
807
|
+
catch (e) {
|
|
808
|
+
if (e instanceof FormulaError)
|
|
809
|
+
return err(e.code);
|
|
810
|
+
throw e;
|
|
811
|
+
}
|
|
812
|
+
}));
|
|
813
|
+
}
|
|
814
|
+
const grid = rangeValues(node, ctx);
|
|
815
|
+
return grid ?? [[evalNode(node, ctx)]];
|
|
816
|
+
}
|
|
817
|
+
function broadcast(l, r, op) {
|
|
818
|
+
const lr = l.length, lc = l[0]?.length ?? 0;
|
|
819
|
+
const rr = r.length, rc = r[0]?.length ?? 0;
|
|
820
|
+
const rows = lr === 1 ? rr : rr === 1 ? lr : Math.max(lr, rr);
|
|
821
|
+
const cols = lc === 1 ? rc : rc === 1 ? lc : Math.max(lc, rc);
|
|
822
|
+
const pick = (g, gr, gc, i, j) => {
|
|
823
|
+
const y = gr === 1 ? 0 : i;
|
|
824
|
+
const x = gc === 1 ? 0 : j;
|
|
825
|
+
if (y >= gr || x >= gc)
|
|
826
|
+
return err('#N/A');
|
|
827
|
+
return g[y]?.[x] ?? '';
|
|
828
|
+
};
|
|
829
|
+
return Array.from({ length: rows }, (_, i) => Array.from({ length: cols }, (_, j) => op(pick(l, lr, lc, i, j), pick(r, rr, rc, i, j))));
|
|
830
|
+
}
|
|
831
|
+
/** An array function's answer: a grid, or a value where it had none to give. */
|
|
832
|
+
function arrayCall(node, ctx) {
|
|
833
|
+
const { perArg, grids } = collectArgs(node.args, ctx);
|
|
834
|
+
// Errors stay inside the array: FILTER and SORT carry an error cell
|
|
835
|
+
// through as Excel does, and decide for themselves about the rest.
|
|
836
|
+
return ARRAY_FUNCTIONS[node.name]({ flat: perArg.flat(), args: perArg, grids });
|
|
837
|
+
}
|
|
838
|
+
/** Every argument evaluated, keeping range shape for the functions that need it. */
|
|
839
|
+
function collectArgs(args, ctx) {
|
|
240
840
|
const perArg = [];
|
|
241
841
|
const grids = [];
|
|
242
842
|
for (const given of args) {
|
|
843
|
+
// A name bound inside the formula, by LET or as a lambda's parameter,
|
|
844
|
+
// hands over its grid: `LET(r, A1:C1, SUM(r))` adds the range.
|
|
845
|
+
if (given.k === 'name') {
|
|
846
|
+
const local = localOf(ctx, given.name);
|
|
847
|
+
if (isGrid(local)) {
|
|
848
|
+
grids.push(local);
|
|
849
|
+
perArg.push(local.flat());
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
243
853
|
// A name stands for whatever it was defined as. Substituting the node
|
|
244
854
|
// here, before the shape check, is what lets `Sales` in =SUM(Sales) be a
|
|
245
855
|
// whole column rather than the top-left cell a scalar read would give.
|
|
@@ -249,6 +859,13 @@ function evalCall(node, ctx) {
|
|
|
249
859
|
grids.push(grid);
|
|
250
860
|
perArg.push(grid.flat());
|
|
251
861
|
}
|
|
862
|
+
else if (arg.k === 'fn' && REFERENCE_FUNCTIONS.has(arg.name)) {
|
|
863
|
+
// OFFSET and INDIRECT hand their whole rectangle to the caller, so
|
|
864
|
+
// =SUM(OFFSET(A1,0,0,3,1)) adds three cells rather than one.
|
|
865
|
+
const grid = rectGrid(referenceCall(arg, ctx), ctx);
|
|
866
|
+
grids.push(grid);
|
|
867
|
+
perArg.push(grid.flat());
|
|
868
|
+
}
|
|
252
869
|
else if (arg.k === 'table') {
|
|
253
870
|
// A structured reference expands exactly as a range does, which is
|
|
254
871
|
// what makes =SUM(Orders[Amount]) work.
|
|
@@ -262,21 +879,67 @@ function evalCall(node, ctx) {
|
|
|
262
879
|
perArg.push(grid.flat());
|
|
263
880
|
}
|
|
264
881
|
}
|
|
882
|
+
else if (arg.k === 'spill') {
|
|
883
|
+
// The spilled range hands the whole array to the caller, so
|
|
884
|
+
// =SUM(E1#) adds every cell the dynamic array reaches.
|
|
885
|
+
const rect = spillRectOf(arg, ctx);
|
|
886
|
+
if (!rect) {
|
|
887
|
+
grids.push(null);
|
|
888
|
+
perArg.push([err('#REF!')]);
|
|
889
|
+
}
|
|
890
|
+
else {
|
|
891
|
+
const grid = rectGrid(rect, ctx);
|
|
892
|
+
grids.push(grid);
|
|
893
|
+
perArg.push(grid.flat());
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
else if (arg.k === 'ref3d') {
|
|
897
|
+
// A 3D reference hands over its cell on every sheet in the range, so
|
|
898
|
+
// =SUM(Sheet1:Sheet3!A1) adds the same cell down the tabs.
|
|
899
|
+
const vals = values3d(arg, ctx);
|
|
900
|
+
grids.push(null);
|
|
901
|
+
perArg.push(vals ?? [err('#REF!')]);
|
|
902
|
+
}
|
|
903
|
+
else if (arg.k === 'fn' && ARRAY_FUNCTIONS[arg.name]) {
|
|
904
|
+
// An array function nested in another hands over its grid, so
|
|
905
|
+
// =SORT(FILTER(...)) and =SUM(SEQUENCE(10)) work.
|
|
906
|
+
const grid = arrayCall(arg, ctx);
|
|
907
|
+
if (Array.isArray(grid)) {
|
|
908
|
+
grids.push(grid);
|
|
909
|
+
perArg.push(grid.flat());
|
|
910
|
+
}
|
|
911
|
+
else {
|
|
912
|
+
grids.push(null);
|
|
913
|
+
perArg.push([grid]);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
else if (arg.k === 'fn' && LAMBDA_HELPERS.has(arg.name)) {
|
|
917
|
+
// And so do the lambda helpers: =SUM(MAP(A1:A9, LAMBDA(v, v * 2))) is
|
|
918
|
+
// the sum of every doubled value, not of the first one. REDUCE hands
|
|
919
|
+
// back one value rather than a grid, which is the same branch.
|
|
920
|
+
const grid = lambdaCall(arg, ctx);
|
|
921
|
+
if (Array.isArray(grid)) {
|
|
922
|
+
grids.push(grid);
|
|
923
|
+
perArg.push(grid.flat());
|
|
924
|
+
}
|
|
925
|
+
else {
|
|
926
|
+
grids.push(null);
|
|
927
|
+
perArg.push([grid]);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
else if ((arg.k === 'binary' || arg.k === 'unary') && hasArray(arg, ctx)) {
|
|
931
|
+
// Arithmetic over a range is a grid, cell by cell: the `B2:B9>3` a
|
|
932
|
+
// FILTER takes, or `A1:A9*2` on its own.
|
|
933
|
+
const grid = gridOf(arg, ctx);
|
|
934
|
+
grids.push(grid);
|
|
935
|
+
perArg.push(grid.flat());
|
|
936
|
+
}
|
|
265
937
|
else {
|
|
266
938
|
grids.push(null);
|
|
267
939
|
perArg.push([evalNode(arg, ctx)]);
|
|
268
940
|
}
|
|
269
941
|
}
|
|
270
|
-
|
|
271
|
-
// An error anywhere in the arguments propagates, except for the counting
|
|
272
|
-
// functions, which Excel lets see errors in their range.
|
|
273
|
-
if (name !== 'COUNTA' && name !== 'COUNTBLANK') {
|
|
274
|
-
for (const v of flat)
|
|
275
|
-
if (isError(v))
|
|
276
|
-
return v;
|
|
277
|
-
}
|
|
278
|
-
const payload = { flat, args: perArg, grids };
|
|
279
|
-
return fn(payload);
|
|
942
|
+
return { perArg, grids };
|
|
280
943
|
}
|
|
281
944
|
/**
|
|
282
945
|
* Evaluate a parsed formula. Never throws: a `FormulaError` raised anywhere
|
|
@@ -293,10 +956,32 @@ export function rangeValues(node, ctx) {
|
|
|
293
956
|
return rangeGrid(node.from, node.to, ctx);
|
|
294
957
|
if (node.k === 'ref')
|
|
295
958
|
return [[ctx.resolve(node.ref.sheet, node.ref.row ?? 0, node.ref.col)]];
|
|
959
|
+
if (node.k === 'spill') {
|
|
960
|
+
const rect = spillRectOf(node, ctx);
|
|
961
|
+
return rect ? rectGrid(rect, ctx) : [[err('#REF!')]];
|
|
962
|
+
}
|
|
296
963
|
if (node.k === 'name') {
|
|
964
|
+
const local = localOf(ctx, node.name);
|
|
965
|
+
if (isGrid(local))
|
|
966
|
+
return local;
|
|
297
967
|
const target = nameTarget(node.name, ctx);
|
|
298
968
|
return target ? rangeValues(target, ctx) : null;
|
|
299
969
|
}
|
|
970
|
+
if (node.k === 'fn' && REFERENCE_FUNCTIONS.has(node.name))
|
|
971
|
+
return rectGrid(referenceCall(node, ctx), ctx);
|
|
972
|
+
if (node.k === 'fn' && ARRAY_FUNCTIONS[node.name]) {
|
|
973
|
+
const grid = arrayCall(node, ctx);
|
|
974
|
+
return Array.isArray(grid) ? grid : [[grid]];
|
|
975
|
+
}
|
|
976
|
+
if (node.k === 'fn' && LAMBDA_HELPERS.has(node.name)) {
|
|
977
|
+
const grid = lambdaCall(node, ctx);
|
|
978
|
+
return Array.isArray(grid) ? grid : [[grid]];
|
|
979
|
+
}
|
|
980
|
+
// `=LET(r, A1:A9, SORT(r))` spills what its calculation is.
|
|
981
|
+
if (node.k === 'fn' && node.name === 'LET') {
|
|
982
|
+
const grid = letGrid(node.args, ctx);
|
|
983
|
+
return grid ?? [[evalNode(node, ctx)]];
|
|
984
|
+
}
|
|
300
985
|
return null;
|
|
301
986
|
}
|
|
302
987
|
catch (e) {
|
|
@@ -305,6 +990,33 @@ export function rangeValues(node, ctx) {
|
|
|
305
990
|
throw e;
|
|
306
991
|
}
|
|
307
992
|
}
|
|
993
|
+
/**
|
|
994
|
+
* The grid a formula spills, or null when it is a plain value: a range on
|
|
995
|
+
* its own (`=A1:A5`), a name that refers to one, a reference function's
|
|
996
|
+
* rectangle, or an array function's answer, when that is more than one
|
|
997
|
+
* cell. A throw inside is the error the cell shows, and no spill.
|
|
998
|
+
*/
|
|
999
|
+
export function evaluateSpill(node, ctx) {
|
|
1000
|
+
try {
|
|
1001
|
+
let grid = null;
|
|
1002
|
+
if (node.k === 'range' || node.k === 'name' || node.k === 'spill'
|
|
1003
|
+
|| (node.k === 'fn' && (REFERENCE_FUNCTIONS.has(node.name) || ARRAY_FUNCTIONS[node.name] || LAMBDA_HELPERS.has(node.name) || node.name === 'LET'))) {
|
|
1004
|
+
grid = rangeValues(node, ctx);
|
|
1005
|
+
}
|
|
1006
|
+
else if ((node.k === 'binary' || node.k === 'unary') && hasArray(node, ctx)) {
|
|
1007
|
+
grid = gridOf(node, ctx);
|
|
1008
|
+
}
|
|
1009
|
+
if (!grid || (grid.length === 1 && (grid[0]?.length ?? 0) <= 1))
|
|
1010
|
+
return null;
|
|
1011
|
+
const width = grid.reduce((m, row) => Math.max(m, row.length), 0);
|
|
1012
|
+
return grid.map((row) => Array.from({ length: width }, (_, i) => row[i] ?? ''));
|
|
1013
|
+
}
|
|
1014
|
+
catch (e) {
|
|
1015
|
+
if (e instanceof FormulaError)
|
|
1016
|
+
return null;
|
|
1017
|
+
throw e;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
308
1020
|
export function evaluate(node, ctx) {
|
|
309
1021
|
try {
|
|
310
1022
|
return evalNode(node, ctx);
|