@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
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Excel's Database family: the twelve aggregates that read a labelled
|
|
3
|
+
* block of records through a labelled block of criteria.
|
|
4
|
+
*
|
|
5
|
+
* All twelve share one shape, `D<agg>(database, field, criteria)`, and all
|
|
6
|
+
* the interesting behaviour is in how the criteria block is read:
|
|
7
|
+
*
|
|
8
|
+
* - Its first row is header names, matched against the database's own
|
|
9
|
+
* headers by text, ignoring case. A header the database does not have
|
|
10
|
+
* matches nothing.
|
|
11
|
+
* - Every row UNDER the header is one alternative: the conditions across
|
|
12
|
+
* a row are ANDed, and the rows are ORed. That is Excel's advanced
|
|
13
|
+
* filter grammar, and it is why the criteria block is two-dimensional
|
|
14
|
+
* rather than a list.
|
|
15
|
+
* - An empty criteria cell places no condition, so a criteria block that
|
|
16
|
+
* is nothing but headers matches every record.
|
|
17
|
+
*
|
|
18
|
+
* `field` is either a header's text or its one-based position. Excel takes
|
|
19
|
+
* both, and a model built by hand usually holds the text while one built
|
|
20
|
+
* by a formula holds the number.
|
|
21
|
+
*
|
|
22
|
+
* These are the only aggregates that need the 2-D shape of their range
|
|
23
|
+
* rather than its flattened values, so they read `a.grids` throughout.
|
|
24
|
+
*/
|
|
25
|
+
import { err, isError } from '../ast.js';
|
|
26
|
+
import { toText, matchesCriterion, numericOnly, isBlank } from '../coerce.js';
|
|
27
|
+
const nth = (a, i) => a.args[i]?.[0] ?? '';
|
|
28
|
+
/** A header row into a lookup from its lower-cased label to its column. */
|
|
29
|
+
function headerIndex(header) {
|
|
30
|
+
const out = new Map();
|
|
31
|
+
header.forEach((label, i) => {
|
|
32
|
+
const key = toText(label).trim().toLowerCase();
|
|
33
|
+
// The first of a duplicated label wins, as Excel's own resolution does.
|
|
34
|
+
if (key !== '' && !out.has(key))
|
|
35
|
+
out.set(key, i);
|
|
36
|
+
});
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
/** The column `field` names, or -1 when it names none. */
|
|
40
|
+
function fieldColumn(field, header) {
|
|
41
|
+
if (typeof field === 'number') {
|
|
42
|
+
const at = Math.trunc(field) - 1;
|
|
43
|
+
return at >= 0 && at < header.length ? at : -1;
|
|
44
|
+
}
|
|
45
|
+
const key = toText(field).trim().toLowerCase();
|
|
46
|
+
return headerIndex(header).get(key) ?? -1;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The records that satisfy the criteria block, as rows of the database.
|
|
50
|
+
*
|
|
51
|
+
* A criteria row with every cell blank matches everything, which is what
|
|
52
|
+
* Excel does and what makes a criteria block of headers alone a no-op.
|
|
53
|
+
*/
|
|
54
|
+
function selected(database, criteria) {
|
|
55
|
+
const dbHeader = database[0] ?? [];
|
|
56
|
+
const index = headerIndex(dbHeader);
|
|
57
|
+
const critHeader = criteria[0] ?? [];
|
|
58
|
+
const records = database.slice(1);
|
|
59
|
+
const rules = criteria.slice(1);
|
|
60
|
+
if (rules.length === 0)
|
|
61
|
+
return [...records];
|
|
62
|
+
// Each criteria row becomes a list of (column, condition) pairs once,
|
|
63
|
+
// rather than once per record.
|
|
64
|
+
const alternatives = rules.map((row) => critHeader.flatMap((label, c) => {
|
|
65
|
+
const condition = row[c];
|
|
66
|
+
if (condition === undefined || isBlank(condition))
|
|
67
|
+
return [];
|
|
68
|
+
const column = index.get(toText(label).trim().toLowerCase());
|
|
69
|
+
// A criteria header the database does not carry can never match.
|
|
70
|
+
return [{ column: column ?? -1, condition }];
|
|
71
|
+
}));
|
|
72
|
+
return records.filter((record) => alternatives.some((row) => row.every(({ column, condition }) => column >= 0 && matchesCriterion(record[column] ?? '', condition))));
|
|
73
|
+
}
|
|
74
|
+
/** The values of `field` across the records that match. */
|
|
75
|
+
function column(a) {
|
|
76
|
+
const database = a.grids[0];
|
|
77
|
+
const criteria = a.grids[2];
|
|
78
|
+
if (!database || !criteria)
|
|
79
|
+
return err('#VALUE!');
|
|
80
|
+
const header = database[0] ?? [];
|
|
81
|
+
const at = fieldColumn(nth(a, 1), header);
|
|
82
|
+
if (at < 0)
|
|
83
|
+
return err('#VALUE!');
|
|
84
|
+
const out = [];
|
|
85
|
+
for (const record of selected(database, criteria)) {
|
|
86
|
+
const v = record[at] ?? '';
|
|
87
|
+
if (isError(v))
|
|
88
|
+
return v;
|
|
89
|
+
out.push(v);
|
|
90
|
+
}
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
/** Wrap an aggregate so it sees only the matching values. */
|
|
94
|
+
const over = (agg) => (a) => {
|
|
95
|
+
const values = column(a);
|
|
96
|
+
return Array.isArray(values) ? agg(values) : values;
|
|
97
|
+
};
|
|
98
|
+
/** The same, over the numbers among them, with Excel's empty-set answer. */
|
|
99
|
+
const overNumbers = (agg, empty = err('#NUM!')) => over((values) => {
|
|
100
|
+
const ns = numericOnly(values);
|
|
101
|
+
return ns.length === 0 ? empty : agg(ns);
|
|
102
|
+
});
|
|
103
|
+
function variance(values, sample) {
|
|
104
|
+
const n = values.length;
|
|
105
|
+
if (n < (sample ? 2 : 1))
|
|
106
|
+
return err('#DIV/0!');
|
|
107
|
+
const mean = values.reduce((x, y) => x + y, 0) / n;
|
|
108
|
+
const ss = values.reduce((acc, v) => acc + (v - mean) ** 2, 0);
|
|
109
|
+
return ss / (sample ? n - 1 : n);
|
|
110
|
+
}
|
|
111
|
+
const stdev = (values, sample) => {
|
|
112
|
+
const v = variance(values, sample);
|
|
113
|
+
return typeof v === 'number' ? Math.sqrt(v) : v;
|
|
114
|
+
};
|
|
115
|
+
export const DATABASE_FUNCTIONS = {
|
|
116
|
+
DSUM: overNumbers((ns) => ns.reduce((x, y) => x + y, 0), 0),
|
|
117
|
+
DPRODUCT: overNumbers((ns) => ns.reduce((x, y) => x * y, 1), 0),
|
|
118
|
+
DAVERAGE: overNumbers((ns) => ns.reduce((x, y) => x + y, 0) / ns.length, err('#DIV/0!')),
|
|
119
|
+
DMAX: overNumbers((ns) => Math.max(...ns), 0),
|
|
120
|
+
DMIN: overNumbers((ns) => Math.min(...ns), 0),
|
|
121
|
+
// DCOUNT counts the numbers; DCOUNTA counts anything that is not blank.
|
|
122
|
+
DCOUNT: over((values) => numericOnly(values).length),
|
|
123
|
+
DCOUNTA: over((values) => values.filter((v) => !isBlank(v)).length),
|
|
124
|
+
DSTDEV: overNumbers((ns) => stdev(ns, true), err('#DIV/0!')),
|
|
125
|
+
DSTDEVP: overNumbers((ns) => stdev(ns, false), err('#DIV/0!')),
|
|
126
|
+
DVAR: overNumbers((ns) => variance(ns, true), err('#DIV/0!')),
|
|
127
|
+
DVARP: overNumbers((ns) => variance(ns, false), err('#DIV/0!')),
|
|
128
|
+
// DGET is the odd one: it wants exactly one match, and says so when the
|
|
129
|
+
// count is anything else.
|
|
130
|
+
DGET: over((values) => {
|
|
131
|
+
const found = values.filter((v) => !isBlank(v));
|
|
132
|
+
if (found.length === 0)
|
|
133
|
+
return err('#VALUE!');
|
|
134
|
+
if (found.length > 1)
|
|
135
|
+
return err('#NUM!');
|
|
136
|
+
return found[0];
|
|
137
|
+
}),
|
|
138
|
+
};
|
|
139
|
+
/** Exported for the tests, which check the criteria grammar on its own. */
|
|
140
|
+
export const __selectRecords = selected;
|
|
141
|
+
/** Exported for the tests: `field` resolution takes text or a position. */
|
|
142
|
+
export const __fieldColumn = fieldColumn;
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Excel's Engineering family: base conversion, the bitwise set, the unit
|
|
3
|
+
* converter, the two step functions and the error integrals.
|
|
4
|
+
*
|
|
5
|
+
* Base conversion is where Excel is least like a programming language, and
|
|
6
|
+
* the differences are the whole reason this module is careful:
|
|
7
|
+
*
|
|
8
|
+
* - A negative number is TWO'S COMPLEMENT in a fixed ten digits, so
|
|
9
|
+
* `=DEC2BIN(-1)` is "1111111111" and not "-1". Ten digits means ten
|
|
10
|
+
* bits for binary, thirty for octal and forty for hex, which is where
|
|
11
|
+
* each function's range comes from.
|
|
12
|
+
* - `places` pads with leading zeros and is IGNORED for a negative
|
|
13
|
+
* number, since the two's complement already fills the width. Too few
|
|
14
|
+
* places for the digits needed is `#NUM!`, not a truncation.
|
|
15
|
+
* - The bitwise functions work on unsigned integers below 2^48, which is
|
|
16
|
+
* past what JavaScript's own bitwise operators can hold, so they run
|
|
17
|
+
* through BigInt rather than `&` and `|`.
|
|
18
|
+
*/
|
|
19
|
+
import { err } from '../ast.js';
|
|
20
|
+
import { toNumber, toText } from '../coerce.js';
|
|
21
|
+
import { erf, erfc } from './special.js';
|
|
22
|
+
const nth = (a, i) => a.args[i]?.[0] ?? '';
|
|
23
|
+
const num = (a, i) => toNumber(nth(a, i));
|
|
24
|
+
const text = (a, i) => toText(nth(a, i));
|
|
25
|
+
const given = (a, i) => {
|
|
26
|
+
const v = a.args[i]?.[0];
|
|
27
|
+
return v !== undefined && v !== '';
|
|
28
|
+
};
|
|
29
|
+
/** Ten digits of this base is how many bits. */
|
|
30
|
+
const WIDTH = { 2: 10, 8: 30, 16: 40 };
|
|
31
|
+
/** Excel's ten-digit window for a base, as [min, max]. */
|
|
32
|
+
function range(base) {
|
|
33
|
+
const half = Math.pow(2, WIDTH[base] - 1);
|
|
34
|
+
return [-half, half - 1];
|
|
35
|
+
}
|
|
36
|
+
/** A number into `base`, ten-digit two's complement for a negative. */
|
|
37
|
+
function toBase(value, base, places) {
|
|
38
|
+
const n = Math.trunc(value);
|
|
39
|
+
const [lo, hi] = range(base);
|
|
40
|
+
if (n < lo || n > hi)
|
|
41
|
+
return err('#NUM!');
|
|
42
|
+
if (n < 0) {
|
|
43
|
+
// Two's complement fills the full width, so `places` has no say.
|
|
44
|
+
const wrapped = n + Math.pow(2, WIDTH[base]);
|
|
45
|
+
return wrapped.toString(base).toUpperCase();
|
|
46
|
+
}
|
|
47
|
+
const digits = n.toString(base).toUpperCase();
|
|
48
|
+
if (places === undefined || places === '')
|
|
49
|
+
return digits;
|
|
50
|
+
const want = Math.trunc(toNumber(places));
|
|
51
|
+
if (want < 0 || want > 10 || want < digits.length)
|
|
52
|
+
return err('#NUM!');
|
|
53
|
+
return digits.padStart(want, '0');
|
|
54
|
+
}
|
|
55
|
+
/** A string of `base` digits back to a number, reading the top bit as a sign. */
|
|
56
|
+
function fromBase(raw, base) {
|
|
57
|
+
const s = raw.trim().toUpperCase();
|
|
58
|
+
if (s === '')
|
|
59
|
+
return 0;
|
|
60
|
+
if (s.length > 10)
|
|
61
|
+
return err('#NUM!');
|
|
62
|
+
const valid = base === 2 ? /^[01]+$/ : base === 8 ? /^[0-7]+$/ : /^[0-9A-F]+$/;
|
|
63
|
+
if (!valid.test(s))
|
|
64
|
+
return err('#NUM!');
|
|
65
|
+
const value = parseInt(s, base);
|
|
66
|
+
// Only a full-width word carries a sign bit, which is why "11" is 3 and
|
|
67
|
+
// "1111111111" is -1.
|
|
68
|
+
if (s.length === 10 && value >= Math.pow(2, WIDTH[base] - 1)) {
|
|
69
|
+
return value - Math.pow(2, WIDTH[base]);
|
|
70
|
+
}
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
/** A base-to-base conversion, which Excel routes through the decimal. */
|
|
74
|
+
const convert = (from, to) => (a) => {
|
|
75
|
+
const value = fromBase(text(a, 0), from);
|
|
76
|
+
if (typeof value !== 'number')
|
|
77
|
+
return value;
|
|
78
|
+
return toBase(value, to, given(a, 1) ? nth(a, 1) : undefined);
|
|
79
|
+
};
|
|
80
|
+
/** The bitwise set's shared guard: a non-negative integer below 2^48. */
|
|
81
|
+
function bitOperand(value) {
|
|
82
|
+
if (!Number.isFinite(value))
|
|
83
|
+
return null;
|
|
84
|
+
const n = Math.trunc(value);
|
|
85
|
+
if (n !== value || n < 0 || n > 281474976710655)
|
|
86
|
+
return null;
|
|
87
|
+
return BigInt(n);
|
|
88
|
+
}
|
|
89
|
+
const bitwise = (op) => (a) => {
|
|
90
|
+
const x = bitOperand(num(a, 0));
|
|
91
|
+
const y = bitOperand(num(a, 1));
|
|
92
|
+
if (x === null || y === null)
|
|
93
|
+
return err('#NUM!');
|
|
94
|
+
return Number(op(x, y));
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* CONVERT's unit table, as a factor into each measure's base unit.
|
|
98
|
+
*
|
|
99
|
+
* Temperature is the exception and is handled separately, because it is an
|
|
100
|
+
* affine conversion rather than a scaling: Celsius to Fahrenheit is not a
|
|
101
|
+
* multiplication.
|
|
102
|
+
*/
|
|
103
|
+
const UNITS = {};
|
|
104
|
+
const unit = (measure, factor, ...names) => {
|
|
105
|
+
for (const n of names)
|
|
106
|
+
UNITS[n] = { measure, factor };
|
|
107
|
+
};
|
|
108
|
+
// Mass, base kilogram.
|
|
109
|
+
unit('mass', 0.001, 'g');
|
|
110
|
+
unit('mass', 1, 'kg');
|
|
111
|
+
unit('mass', 0.45359237, 'lbm');
|
|
112
|
+
unit('mass', 0.45359237 / 16, 'ozm');
|
|
113
|
+
unit('mass', 1.6605402e-27, 'u');
|
|
114
|
+
unit('mass', 0.0000647989, 'grain');
|
|
115
|
+
unit('mass', 907.18474, 'ton');
|
|
116
|
+
unit('mass', 1016.0469088, 'uk_ton', 'LTON', 'brton');
|
|
117
|
+
unit('mass', 6.35029318, 'stone');
|
|
118
|
+
unit('mass', 0.0000020, 'sg');
|
|
119
|
+
// Distance, base metre.
|
|
120
|
+
unit('distance', 1, 'm');
|
|
121
|
+
unit('distance', 0.0254, 'in');
|
|
122
|
+
unit('distance', 0.3048, 'ft');
|
|
123
|
+
unit('distance', 0.9144, 'yd');
|
|
124
|
+
unit('distance', 1609.344, 'mi');
|
|
125
|
+
unit('distance', 1852, 'Nmi');
|
|
126
|
+
unit('distance', 1e-10, 'ang');
|
|
127
|
+
unit('distance', 0.0037, 'pica');
|
|
128
|
+
unit('distance', 0.0254 / 72, 'Pica');
|
|
129
|
+
unit('distance', 9.46073047258e15, 'ly');
|
|
130
|
+
unit('distance', 3.08567758149137e16, 'parsec', 'pc');
|
|
131
|
+
unit('distance', 201.168, 'survey_mi');
|
|
132
|
+
// Time, base second.
|
|
133
|
+
unit('time', 31557600, 'yr');
|
|
134
|
+
unit('time', 86400, 'day', 'd');
|
|
135
|
+
unit('time', 3600, 'hr');
|
|
136
|
+
unit('time', 60, 'mn', 'min');
|
|
137
|
+
unit('time', 1, 'sec', 's');
|
|
138
|
+
// Pressure, base pascal.
|
|
139
|
+
unit('pressure', 1, 'Pa', 'p');
|
|
140
|
+
unit('pressure', 101325, 'atm', 'at');
|
|
141
|
+
unit('pressure', 133.322368421, 'mmHg');
|
|
142
|
+
unit('pressure', 6894.75729317, 'psi');
|
|
143
|
+
unit('pressure', 100000, 'Torr');
|
|
144
|
+
// Force, base newton.
|
|
145
|
+
unit('force', 1, 'N');
|
|
146
|
+
unit('force', 0.00001, 'dyn', 'dy');
|
|
147
|
+
unit('force', 4.4482216152605, 'lbf');
|
|
148
|
+
unit('force', 9.80665, 'pond');
|
|
149
|
+
// Energy, base joule.
|
|
150
|
+
unit('energy', 1, 'J');
|
|
151
|
+
unit('energy', 0.0000001, 'e');
|
|
152
|
+
unit('energy', 1.05505585262e3, 'BTU', 'btu');
|
|
153
|
+
unit('energy', 4.1868, 'c');
|
|
154
|
+
unit('energy', 4.184, 'cal');
|
|
155
|
+
unit('energy', 1.60217733e-19, 'eV', 'ev');
|
|
156
|
+
unit('energy', 1.3558179483314, 'flb');
|
|
157
|
+
unit('energy', 3600000, 'Wh', 'wh');
|
|
158
|
+
unit('energy', 101.3252, 'HPh', 'hh');
|
|
159
|
+
// Power, base watt.
|
|
160
|
+
unit('power', 1, 'W', 'w');
|
|
161
|
+
unit('power', 745.69987158227, 'HP', 'h');
|
|
162
|
+
unit('power', 0.73549875, 'PS');
|
|
163
|
+
// Magnetism, base tesla.
|
|
164
|
+
unit('magnetism', 1, 'T');
|
|
165
|
+
unit('magnetism', 0.0001, 'ga');
|
|
166
|
+
// Volume, base litre.
|
|
167
|
+
unit('volume', 1, 'l', 'L', 'lt');
|
|
168
|
+
unit('volume', 0.0000163870640693, 'in3', 'in^3');
|
|
169
|
+
unit('volume', 28.316846592, 'ft3', 'ft^3');
|
|
170
|
+
unit('volume', 764.554857984, 'yd3', 'yd^3');
|
|
171
|
+
unit('volume', 1000, 'm3', 'm^3');
|
|
172
|
+
unit('volume', 3.785411784, 'gal');
|
|
173
|
+
unit('volume', 0.94635295, 'qt');
|
|
174
|
+
unit('volume', 0.473176473, 'pt', 'us_pt');
|
|
175
|
+
unit('volume', 0.5682612532, 'uk_pt');
|
|
176
|
+
unit('volume', 0.2365882365, 'cup');
|
|
177
|
+
unit('volume', 0.0295735295625, 'oz');
|
|
178
|
+
unit('volume', 0.01478676478125, 'tbs');
|
|
179
|
+
unit('volume', 0.00492892159375, 'tsp');
|
|
180
|
+
unit('volume', 0.00492892159375, 'tspm');
|
|
181
|
+
unit('volume', 158.987294928, 'barrel', 'bbl');
|
|
182
|
+
unit('volume', 119240.471196, 'MTON');
|
|
183
|
+
unit('volume', 158.987294928 * 6.28981, 'GRT', 'regton');
|
|
184
|
+
// Area, base square metre.
|
|
185
|
+
unit('area', 1, 'm2', 'm^2');
|
|
186
|
+
unit('area', 4046.8564224, 'acre', 'uk_acre', 'us_acre');
|
|
187
|
+
unit('area', 0.00064516, 'in2', 'in^2');
|
|
188
|
+
unit('area', 0.09290304, 'ft2', 'ft^2');
|
|
189
|
+
unit('area', 0.83612736, 'yd2', 'yd^2');
|
|
190
|
+
unit('area', 2589988.110336, 'mi2', 'mi^2');
|
|
191
|
+
unit('area', 10000, 'ha');
|
|
192
|
+
unit('area', 1e-28, 'ang2', 'ang^2');
|
|
193
|
+
// Information, base bit.
|
|
194
|
+
unit('information', 1, 'bit');
|
|
195
|
+
unit('information', 8, 'byte');
|
|
196
|
+
// Speed, base metres per second.
|
|
197
|
+
unit('speed', 1, 'm/s', 'm/sec');
|
|
198
|
+
unit('speed', 1 / 3.6, 'm/h', 'm/hr');
|
|
199
|
+
unit('speed', 1609.344 / 3600, 'mph');
|
|
200
|
+
unit('speed', 1852 / 3600, 'kn', 'admkn');
|
|
201
|
+
/** The SI prefixes CONVERT accepts in front of a metric unit. */
|
|
202
|
+
const PREFIXES = {
|
|
203
|
+
Y: 1e24, Z: 1e21, E: 1e18, P: 1e15, T: 1e12, G: 1e9, M: 1e6, k: 1e3, h: 1e2,
|
|
204
|
+
e: 1e1, d: 1e-1, c: 1e-2, m: 1e-3, u: 1e-6, n: 1e-9, p: 1e-12, f: 1e-15,
|
|
205
|
+
a: 1e-18, z: 1e-21, y: 1e-24,
|
|
206
|
+
};
|
|
207
|
+
/** A unit name into its measure and its factor, prefix allowed for. */
|
|
208
|
+
function lookup(name) {
|
|
209
|
+
const direct = UNITS[name];
|
|
210
|
+
if (direct)
|
|
211
|
+
return direct;
|
|
212
|
+
// A prefix only counts when what follows it is a unit in its own right.
|
|
213
|
+
const head = name.slice(0, 1);
|
|
214
|
+
const rest = name.slice(1);
|
|
215
|
+
const scale = PREFIXES[head];
|
|
216
|
+
const base = scale === undefined ? undefined : UNITS[rest];
|
|
217
|
+
if (scale !== undefined && base)
|
|
218
|
+
return { measure: base.measure, factor: base.factor * scale };
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
/** Temperature, which scales AND shifts, so it sits outside the table. */
|
|
222
|
+
const TEMPERATURE = {
|
|
223
|
+
C: { toK: (v) => v + 273.15, fromK: (v) => v - 273.15 },
|
|
224
|
+
F: { toK: (v) => ((v - 32) * 5) / 9 + 273.15, fromK: (v) => ((v - 273.15) * 9) / 5 + 32 },
|
|
225
|
+
K: { toK: (v) => v, fromK: (v) => v },
|
|
226
|
+
Rank: { toK: (v) => (v * 5) / 9, fromK: (v) => (v * 9) / 5 },
|
|
227
|
+
Reau: { toK: (v) => v * 1.25 + 273.15, fromK: (v) => (v - 273.15) * 0.8 },
|
|
228
|
+
};
|
|
229
|
+
TEMPERATURE.cel = TEMPERATURE.C;
|
|
230
|
+
TEMPERATURE.fah = TEMPERATURE.F;
|
|
231
|
+
TEMPERATURE.kel = TEMPERATURE.K;
|
|
232
|
+
export const ENGINEERING_FUNCTIONS = {
|
|
233
|
+
// ---- Base conversion -------------------------------------------------
|
|
234
|
+
DEC2BIN: (a) => toBase(num(a, 0), 2, given(a, 1) ? nth(a, 1) : undefined),
|
|
235
|
+
DEC2OCT: (a) => toBase(num(a, 0), 8, given(a, 1) ? nth(a, 1) : undefined),
|
|
236
|
+
DEC2HEX: (a) => toBase(num(a, 0), 16, given(a, 1) ? nth(a, 1) : undefined),
|
|
237
|
+
BIN2DEC: (a) => fromBase(text(a, 0), 2),
|
|
238
|
+
OCT2DEC: (a) => fromBase(text(a, 0), 8),
|
|
239
|
+
HEX2DEC: (a) => fromBase(text(a, 0), 16),
|
|
240
|
+
BIN2OCT: convert(2, 8),
|
|
241
|
+
BIN2HEX: convert(2, 16),
|
|
242
|
+
OCT2BIN: convert(8, 2),
|
|
243
|
+
OCT2HEX: convert(8, 16),
|
|
244
|
+
HEX2BIN: convert(16, 2),
|
|
245
|
+
HEX2OCT: convert(16, 8),
|
|
246
|
+
// ---- Bitwise ---------------------------------------------------------
|
|
247
|
+
BITAND: bitwise((x, y) => x & y),
|
|
248
|
+
BITOR: bitwise((x, y) => x | y),
|
|
249
|
+
BITXOR: bitwise((x, y) => x ^ y),
|
|
250
|
+
BITLSHIFT: (a) => {
|
|
251
|
+
const x = bitOperand(num(a, 0));
|
|
252
|
+
const by = Math.trunc(num(a, 1));
|
|
253
|
+
if (x === null || Math.abs(by) > 53)
|
|
254
|
+
return err('#NUM!');
|
|
255
|
+
const out = by >= 0 ? x << BigInt(by) : x >> BigInt(-by);
|
|
256
|
+
return out > 281474976710655n ? err('#NUM!') : Number(out);
|
|
257
|
+
},
|
|
258
|
+
BITRSHIFT: (a) => {
|
|
259
|
+
const x = bitOperand(num(a, 0));
|
|
260
|
+
const by = Math.trunc(num(a, 1));
|
|
261
|
+
if (x === null || Math.abs(by) > 53)
|
|
262
|
+
return err('#NUM!');
|
|
263
|
+
const out = by >= 0 ? x >> BigInt(by) : x << BigInt(-by);
|
|
264
|
+
return out > 281474976710655n ? err('#NUM!') : Number(out);
|
|
265
|
+
},
|
|
266
|
+
// ---- Step functions --------------------------------------------------
|
|
267
|
+
DELTA: (a) => (num(a, 0) === (given(a, 1) ? num(a, 1) : 0) ? 1 : 0),
|
|
268
|
+
GESTEP: (a) => (num(a, 0) >= (given(a, 1) ? num(a, 1) : 0) ? 1 : 0),
|
|
269
|
+
// ---- Error integrals -------------------------------------------------
|
|
270
|
+
// ERF with two arguments is the integral BETWEEN the limits.
|
|
271
|
+
ERF: (a) => (given(a, 1) ? erf(num(a, 1)) - erf(num(a, 0)) : erf(num(a, 0))),
|
|
272
|
+
'ERF.PRECISE': (a) => erf(num(a, 0)),
|
|
273
|
+
ERFC: (a) => erfc(num(a, 0)),
|
|
274
|
+
'ERFC.PRECISE': (a) => erfc(num(a, 0)),
|
|
275
|
+
// ---- Units -----------------------------------------------------------
|
|
276
|
+
CONVERT: (a) => {
|
|
277
|
+
const value = num(a, 0);
|
|
278
|
+
const from = text(a, 1);
|
|
279
|
+
const to = text(a, 2);
|
|
280
|
+
const tFrom = TEMPERATURE[from];
|
|
281
|
+
const tTo = TEMPERATURE[to];
|
|
282
|
+
if (tFrom || tTo) {
|
|
283
|
+
// Both sides have to be temperatures, or the measures do not agree.
|
|
284
|
+
if (!tFrom || !tTo)
|
|
285
|
+
return err('#N/A');
|
|
286
|
+
return tTo.fromK(tFrom.toK(value));
|
|
287
|
+
}
|
|
288
|
+
const f = lookup(from);
|
|
289
|
+
const t = lookup(to);
|
|
290
|
+
if (!f || !t)
|
|
291
|
+
return err('#N/A');
|
|
292
|
+
if (f.measure !== t.measure)
|
|
293
|
+
return err('#N/A');
|
|
294
|
+
return (value * f.factor) / t.factor;
|
|
295
|
+
},
|
|
296
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { SheetFunction } from '../functions.js';
|
|
2
|
+
export declare function pmt(rate: number, nper: number, pv: number, fv: number, type: 0 | 1): number;
|
|
3
|
+
export declare function fv(rate: number, nper: number, pmt: number, pv: number, type: 0 | 1): number;
|
|
4
|
+
export declare function pv(rate: number, nper: number, pmt: number, fv: number, type: 0 | 1): number;
|
|
5
|
+
export declare const FINANCIAL_FUNCTIONS: Record<string, SheetFunction>;
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Excel's time-value-of-money functions: PMT and its parts, PV, FV, NPER,
|
|
3
|
+
* RATE, NPV, IRR and straight-line depreciation.
|
|
4
|
+
*
|
|
5
|
+
* All of them share Excel's sign convention, which is the thing to get
|
|
6
|
+
* right: money you pay out is negative, money you receive is positive, so
|
|
7
|
+
* `=PMT(5%/12, 360, 200000)` is a negative payment on a positive loan and
|
|
8
|
+
* `=FV(6%/12, 120, -100)` is a positive balance from negative deposits.
|
|
9
|
+
* The closed forms below are the ones Excel documents; RATE and IRR have
|
|
10
|
+
* no closed form and are solved by Newton's method with a bisection
|
|
11
|
+
* fallback, returning `#NUM!` when neither converges, as Excel does.
|
|
12
|
+
*/
|
|
13
|
+
import { err } from '../ast.js';
|
|
14
|
+
import { toNumber, numericOnly } from '../coerce.js';
|
|
15
|
+
const num = (a, i) => toNumber(a.args[i]?.[0] ?? '');
|
|
16
|
+
/** An optional argument: absent, or left empty between commas, reads as
|
|
17
|
+
* `fallback`. A typed 0 is 0. */
|
|
18
|
+
function opt(a, i, fallback) {
|
|
19
|
+
const v = a.args[i]?.[0];
|
|
20
|
+
return v === undefined || v === '' ? fallback : toNumber(v);
|
|
21
|
+
}
|
|
22
|
+
/** `type` is 0 for payments at the end of the period, 1 for the start. */
|
|
23
|
+
const paymentType = (a, i) => (opt(a, i, 0) === 0 ? 0 : 1);
|
|
24
|
+
export function pmt(rate, nper, pv, fv, type) {
|
|
25
|
+
if (rate === 0)
|
|
26
|
+
return -(pv + fv) / nper;
|
|
27
|
+
const f = Math.pow(1 + rate, nper);
|
|
28
|
+
return -(rate * (pv * f + fv)) / ((1 + rate * type) * (f - 1));
|
|
29
|
+
}
|
|
30
|
+
export function fv(rate, nper, pmt, pv, type) {
|
|
31
|
+
if (rate === 0)
|
|
32
|
+
return -(pv + pmt * nper);
|
|
33
|
+
const f = Math.pow(1 + rate, nper);
|
|
34
|
+
return -(pv * f + (pmt * (1 + rate * type) * (f - 1)) / rate);
|
|
35
|
+
}
|
|
36
|
+
export function pv(rate, nper, pmt, fv, type) {
|
|
37
|
+
if (rate === 0)
|
|
38
|
+
return -(fv + pmt * nper);
|
|
39
|
+
const f = Math.pow(1 + rate, nper);
|
|
40
|
+
return -(fv + (pmt * (1 + rate * type) * (f - 1)) / rate) / f;
|
|
41
|
+
}
|
|
42
|
+
function nper(rate, pmt, pv, fv, type) {
|
|
43
|
+
if (rate === 0)
|
|
44
|
+
return pmt === 0 ? err('#NUM!') : -(pv + fv) / pmt;
|
|
45
|
+
const p = pmt * (1 + rate * type);
|
|
46
|
+
const ratio = (p - fv * rate) / (p + pv * rate);
|
|
47
|
+
if (!(ratio > 0))
|
|
48
|
+
return err('#NUM!');
|
|
49
|
+
return Math.log(ratio) / Math.log(1 + rate);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Find a root of `f` near `guess`: Newton with a numerical derivative, then
|
|
53
|
+
* a bracketing scan and bisection when Newton wanders off. Both RATE and
|
|
54
|
+
* IRR are smooth enough for this, and the fallback is what keeps a loan
|
|
55
|
+
* with an odd guess from coming back `#NUM!` when Excel finds it.
|
|
56
|
+
*/
|
|
57
|
+
function solve(f, guess, low, high) {
|
|
58
|
+
let x = guess;
|
|
59
|
+
for (let i = 0; i < 100; i += 1) {
|
|
60
|
+
const y = f(x);
|
|
61
|
+
if (!Number.isFinite(y))
|
|
62
|
+
break;
|
|
63
|
+
if (Math.abs(y) < 1e-10)
|
|
64
|
+
return x;
|
|
65
|
+
const h = Math.max(1e-7, Math.abs(x) * 1e-6);
|
|
66
|
+
const slope = (f(x + h) - f(x - h)) / (2 * h);
|
|
67
|
+
if (!Number.isFinite(slope) || slope === 0)
|
|
68
|
+
break;
|
|
69
|
+
const next = x - y / slope;
|
|
70
|
+
if (!Number.isFinite(next) || next <= low || next >= high)
|
|
71
|
+
break;
|
|
72
|
+
if (Math.abs(next - x) < 1e-12)
|
|
73
|
+
return next;
|
|
74
|
+
x = next;
|
|
75
|
+
}
|
|
76
|
+
// Bracket a sign change on a grid, then bisect it.
|
|
77
|
+
const steps = 400;
|
|
78
|
+
let a = low;
|
|
79
|
+
let fa = f(a);
|
|
80
|
+
for (let i = 1; i <= steps; i += 1) {
|
|
81
|
+
const b = low + ((high - low) * i) / steps;
|
|
82
|
+
const fb = f(b);
|
|
83
|
+
if (Number.isFinite(fa) && Number.isFinite(fb) && Math.sign(fa) !== Math.sign(fb)) {
|
|
84
|
+
let lo = a;
|
|
85
|
+
let hi = b;
|
|
86
|
+
let flo = fa;
|
|
87
|
+
for (let k = 0; k < 200; k += 1) {
|
|
88
|
+
const mid = (lo + hi) / 2;
|
|
89
|
+
const fm = f(mid);
|
|
90
|
+
if (Math.abs(fm) < 1e-10 || hi - lo < 1e-14)
|
|
91
|
+
return mid;
|
|
92
|
+
if (Math.sign(fm) === Math.sign(flo)) {
|
|
93
|
+
lo = mid;
|
|
94
|
+
flo = fm;
|
|
95
|
+
}
|
|
96
|
+
else
|
|
97
|
+
hi = mid;
|
|
98
|
+
}
|
|
99
|
+
return (lo + hi) / 2;
|
|
100
|
+
}
|
|
101
|
+
a = b;
|
|
102
|
+
fa = fb;
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
function npv(rate, flows) {
|
|
107
|
+
let total = 0;
|
|
108
|
+
for (let i = 0; i < flows.length; i += 1)
|
|
109
|
+
total += flows[i] / Math.pow(1 + rate, i + 1);
|
|
110
|
+
return total;
|
|
111
|
+
}
|
|
112
|
+
/** The balance a loan carries into period `per`, which IPMT charges
|
|
113
|
+
* interest on and PPMT pays down: the future value after per-1 payments. */
|
|
114
|
+
function balanceBefore(rate, per, payment, present, type) {
|
|
115
|
+
if (per === 1)
|
|
116
|
+
return type === 1 ? 0 : -present;
|
|
117
|
+
if (type === 1)
|
|
118
|
+
return fv(rate, per - 2, payment, present, 1) - payment;
|
|
119
|
+
return fv(rate, per - 1, payment, present, 0);
|
|
120
|
+
}
|
|
121
|
+
export const FINANCIAL_FUNCTIONS = {
|
|
122
|
+
PMT: (a) => {
|
|
123
|
+
const rate = num(a, 0);
|
|
124
|
+
const n = num(a, 1);
|
|
125
|
+
if (n === 0 || rate <= -1)
|
|
126
|
+
return err('#NUM!');
|
|
127
|
+
return pmt(rate, n, num(a, 2), opt(a, 3, 0), paymentType(a, 4));
|
|
128
|
+
},
|
|
129
|
+
FV: (a) => {
|
|
130
|
+
const rate = num(a, 0);
|
|
131
|
+
if (rate <= -1)
|
|
132
|
+
return err('#NUM!');
|
|
133
|
+
return fv(rate, num(a, 1), num(a, 2), opt(a, 3, 0), paymentType(a, 4));
|
|
134
|
+
},
|
|
135
|
+
PV: (a) => {
|
|
136
|
+
const rate = num(a, 0);
|
|
137
|
+
if (rate <= -1)
|
|
138
|
+
return err('#NUM!');
|
|
139
|
+
return pv(rate, num(a, 1), num(a, 2), opt(a, 3, 0), paymentType(a, 4));
|
|
140
|
+
},
|
|
141
|
+
NPER: (a) => {
|
|
142
|
+
const rate = num(a, 0);
|
|
143
|
+
if (rate <= -1)
|
|
144
|
+
return err('#NUM!');
|
|
145
|
+
return nper(rate, num(a, 1), num(a, 2), opt(a, 3, 0), paymentType(a, 4));
|
|
146
|
+
},
|
|
147
|
+
// IPMT(rate, per, nper, pv, [fv], [type]): the interest part of the
|
|
148
|
+
// payment in period `per`, the balance carried into it times the rate.
|
|
149
|
+
IPMT: (a) => {
|
|
150
|
+
const rate = num(a, 0);
|
|
151
|
+
const per = num(a, 1);
|
|
152
|
+
const n = num(a, 2);
|
|
153
|
+
const present = num(a, 3);
|
|
154
|
+
const type = paymentType(a, 5);
|
|
155
|
+
if (per < 1 || per > n || n === 0 || rate <= -1)
|
|
156
|
+
return err('#NUM!');
|
|
157
|
+
const payment = pmt(rate, n, present, opt(a, 4, 0), type);
|
|
158
|
+
return balanceBefore(rate, per, payment, present, type) * rate;
|
|
159
|
+
},
|
|
160
|
+
PPMT: (a) => {
|
|
161
|
+
const rate = num(a, 0);
|
|
162
|
+
const per = num(a, 1);
|
|
163
|
+
const n = num(a, 2);
|
|
164
|
+
const present = num(a, 3);
|
|
165
|
+
const type = paymentType(a, 5);
|
|
166
|
+
if (per < 1 || per > n || n === 0 || rate <= -1)
|
|
167
|
+
return err('#NUM!');
|
|
168
|
+
const payment = pmt(rate, n, present, opt(a, 4, 0), type);
|
|
169
|
+
return payment - balanceBefore(rate, per, payment, present, type) * rate;
|
|
170
|
+
},
|
|
171
|
+
RATE: (a) => {
|
|
172
|
+
const n = num(a, 0);
|
|
173
|
+
const payment = num(a, 1);
|
|
174
|
+
const present = num(a, 2);
|
|
175
|
+
const future = opt(a, 3, 0);
|
|
176
|
+
const type = paymentType(a, 4);
|
|
177
|
+
const guess = opt(a, 5, 0.1);
|
|
178
|
+
if (n <= 0)
|
|
179
|
+
return err('#NUM!');
|
|
180
|
+
const f = (r) => {
|
|
181
|
+
if (r === 0)
|
|
182
|
+
return present + payment * n + future;
|
|
183
|
+
const g = Math.pow(1 + r, n);
|
|
184
|
+
return present * g + (payment * (1 + r * type) * (g - 1)) / r + future;
|
|
185
|
+
};
|
|
186
|
+
const root = solve(f, guess, -0.999999, 10);
|
|
187
|
+
return root === null ? err('#NUM!') : root;
|
|
188
|
+
},
|
|
189
|
+
// NPV(rate, value1, ...): the first flow is discounted one period, as in
|
|
190
|
+
// Excel, so an investment made today goes outside the call: =NPV(...)+A1.
|
|
191
|
+
NPV: (a) => {
|
|
192
|
+
const rate = num(a, 0);
|
|
193
|
+
if (rate <= -1)
|
|
194
|
+
return err('#NUM!');
|
|
195
|
+
return npv(rate, numericOnly(a.args.slice(1).flat()));
|
|
196
|
+
},
|
|
197
|
+
// IRR(values, [guess]): the rate at which the flows, the first one at
|
|
198
|
+
// time zero, sum to nothing. Needs at least one inflow and one outflow.
|
|
199
|
+
IRR: (a) => {
|
|
200
|
+
const flows = numericOnly(a.args[0] ?? []);
|
|
201
|
+
const guess = opt(a, 1, 0.1);
|
|
202
|
+
if (!flows.some((v) => v > 0) || !flows.some((v) => v < 0))
|
|
203
|
+
return err('#NUM!');
|
|
204
|
+
const f = (r) => {
|
|
205
|
+
let total = 0;
|
|
206
|
+
for (let i = 0; i < flows.length; i += 1)
|
|
207
|
+
total += flows[i] / Math.pow(1 + r, i);
|
|
208
|
+
return total;
|
|
209
|
+
};
|
|
210
|
+
const root = solve(f, guess, -0.999999, 10);
|
|
211
|
+
return root === null ? err('#NUM!') : root;
|
|
212
|
+
},
|
|
213
|
+
// SLN(cost, salvage, life): straight-line depreciation for one period.
|
|
214
|
+
SLN: (a) => {
|
|
215
|
+
const life = num(a, 2);
|
|
216
|
+
if (life === 0)
|
|
217
|
+
return err('#DIV/0!');
|
|
218
|
+
return (num(a, 0) - num(a, 1)) / life;
|
|
219
|
+
},
|
|
220
|
+
};
|