@cj-tech-master/excelts 9.2.1 → 9.3.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/README.md +25 -2
- package/README_zh.md +29 -6
- package/dist/browser/index.browser.d.ts +1 -1
- package/dist/browser/index.browser.js +4 -0
- package/dist/browser/index.d.ts +1 -1
- package/dist/browser/index.js +4 -0
- package/dist/browser/modules/excel/cell.d.ts +17 -3
- package/dist/browser/modules/excel/cell.js +170 -22
- package/dist/browser/modules/excel/defined-names.d.ts +96 -1
- package/dist/browser/modules/excel/defined-names.js +411 -21
- package/dist/browser/modules/excel/image.d.ts +11 -0
- package/dist/browser/modules/excel/image.js +24 -1
- package/dist/browser/modules/excel/stream/workbook-reader.browser.d.ts +9 -3
- package/dist/browser/modules/excel/stream/workbook-reader.browser.js +14 -0
- package/dist/browser/modules/excel/stream/workbook-reader.d.ts +2 -1
- package/dist/browser/modules/excel/stream/workbook-writer.browser.d.ts +39 -5
- package/dist/browser/modules/excel/stream/workbook-writer.browser.js +48 -1
- package/dist/browser/modules/excel/stream/workbook-writer.d.ts +3 -2
- package/dist/browser/modules/excel/stream/worksheet-reader.js +17 -1
- package/dist/browser/modules/excel/stream/worksheet-writer.d.ts +39 -6
- package/dist/browser/modules/excel/stream/worksheet-writer.js +45 -5
- package/dist/browser/modules/excel/table.js +15 -2
- package/dist/browser/modules/excel/types.d.ts +133 -2
- package/dist/browser/modules/excel/utils/col-cache.d.ts +1 -0
- package/dist/browser/modules/excel/utils/col-cache.js +15 -0
- package/dist/browser/modules/excel/utils/drawing-utils.d.ts +3 -3
- package/dist/browser/modules/excel/utils/drawing-utils.js +4 -0
- package/dist/browser/modules/excel/utils/external-link-formula.d.ts +76 -0
- package/dist/browser/modules/excel/utils/external-link-formula.js +208 -0
- package/dist/browser/modules/excel/utils/iterate-stream.d.ts +9 -3
- package/dist/browser/modules/excel/utils/iterate-stream.js +3 -1
- package/dist/browser/modules/excel/utils/ooxml-paths.d.ts +19 -0
- package/dist/browser/modules/excel/utils/ooxml-paths.js +37 -2
- package/dist/browser/modules/excel/utils/shared-strings.d.ts +8 -3
- package/dist/browser/modules/excel/utils/shared-strings.js +21 -2
- package/dist/browser/modules/excel/utils/workbook-protection.d.ts +30 -0
- package/dist/browser/modules/excel/utils/workbook-protection.js +30 -0
- package/dist/browser/modules/excel/workbook.browser.d.ts +257 -6
- package/dist/browser/modules/excel/workbook.browser.js +318 -34
- package/dist/browser/modules/excel/workbook.d.ts +1 -1
- package/dist/browser/modules/excel/worksheet.d.ts +3 -1
- package/dist/browser/modules/excel/worksheet.js +21 -2
- package/dist/browser/modules/excel/xlsx/rel-type.d.ts +15 -0
- package/dist/browser/modules/excel/xlsx/rel-type.js +16 -1
- package/dist/browser/modules/excel/xlsx/xform/book/defined-name-xform.d.ts +6 -5
- package/dist/browser/modules/excel/xlsx/xform/book/defined-name-xform.js +21 -86
- package/dist/browser/modules/excel/xlsx/xform/book/external-link-xform.d.ts +84 -0
- package/dist/browser/modules/excel/xlsx/xform/book/external-link-xform.js +330 -0
- package/dist/browser/modules/excel/xlsx/xform/book/external-reference-xform.d.ts +17 -0
- package/dist/browser/modules/excel/xlsx/xform/book/external-reference-xform.js +24 -0
- package/dist/browser/modules/excel/xlsx/xform/book/workbook-calc-properties-xform.d.ts +3 -0
- package/dist/browser/modules/excel/xlsx/xform/book/workbook-calc-properties-xform.js +11 -2
- package/dist/browser/modules/excel/xlsx/xform/book/workbook-protection-xform.d.ts +20 -0
- package/dist/browser/modules/excel/xlsx/xform/book/workbook-protection-xform.js +66 -0
- package/dist/browser/modules/excel/xlsx/xform/book/workbook-xform.js +38 -5
- package/dist/browser/modules/excel/xlsx/xform/core/content-types-xform.js +19 -1
- package/dist/browser/modules/excel/xlsx/xform/core/metadata-xform.d.ts +56 -0
- package/dist/browser/modules/excel/xlsx/xform/core/metadata-xform.js +158 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.d.ts +26 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +105 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +3 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/drawing-xform.js +10 -2
- package/dist/browser/modules/excel/xlsx/xform/sheet/cell-xform.d.ts +1 -1
- package/dist/browser/modules/excel/xlsx/xform/sheet/cell-xform.js +166 -8
- package/dist/browser/modules/excel/xlsx/xform/sheet/data-validations-xform.js +1 -1
- package/dist/browser/modules/excel/xlsx/xform/sheet/ignored-errors-xform.d.ts +21 -0
- package/dist/browser/modules/excel/xlsx/xform/sheet/ignored-errors-xform.js +80 -0
- package/dist/browser/modules/excel/xlsx/xform/sheet/worksheet-xform.js +9 -4
- package/dist/browser/modules/excel/xlsx/xform/style/border-xform.js +4 -1
- package/dist/browser/modules/excel/xlsx/xlsx.browser.d.ts +172 -13
- package/dist/browser/modules/excel/xlsx/xlsx.browser.js +410 -20
- package/dist/browser/modules/excel/xlsx/xlsx.d.ts +7 -4
- package/dist/browser/modules/excel/xlsx/xlsx.js +4 -5
- package/dist/browser/modules/formula/compile/address-utils.d.ts +62 -0
- package/dist/browser/modules/formula/compile/address-utils.js +83 -0
- package/dist/browser/modules/formula/compile/binder.d.ts +42 -0
- package/dist/browser/modules/formula/compile/binder.js +487 -0
- package/dist/browser/modules/formula/compile/bound-ast.d.ts +230 -0
- package/dist/browser/modules/formula/compile/bound-ast.js +80 -0
- package/dist/browser/modules/formula/compile/compiled-formula.d.ts +137 -0
- package/dist/browser/modules/formula/compile/compiled-formula.js +383 -0
- package/dist/browser/modules/formula/compile/dependency-analysis.d.ts +93 -0
- package/dist/browser/modules/formula/compile/dependency-analysis.js +432 -0
- package/dist/browser/modules/formula/compile/structured-ref-utils.d.ts +93 -0
- package/dist/browser/modules/formula/compile/structured-ref-utils.js +136 -0
- package/dist/browser/modules/formula/default-syntax-probe.d.ts +79 -0
- package/dist/browser/modules/formula/default-syntax-probe.js +83 -0
- package/dist/browser/modules/formula/functions/_date-context.d.ts +4 -0
- package/dist/browser/modules/formula/functions/_date-context.js +29 -0
- package/dist/browser/modules/formula/functions/_shared.d.ts +121 -0
- package/dist/browser/modules/formula/functions/_shared.js +381 -0
- package/dist/browser/modules/formula/functions/conditional.d.ts +27 -0
- package/dist/browser/modules/formula/functions/conditional.js +343 -0
- package/dist/browser/modules/formula/functions/database.d.ts +37 -0
- package/dist/browser/modules/formula/functions/database.js +274 -0
- package/dist/browser/modules/formula/functions/date.d.ts +61 -0
- package/dist/browser/modules/formula/functions/date.js +855 -0
- package/dist/browser/modules/formula/functions/dynamic-array.d.ts +23 -0
- package/dist/browser/modules/formula/functions/dynamic-array.js +860 -0
- package/dist/browser/modules/formula/functions/engineering.d.ts +57 -0
- package/dist/browser/modules/formula/functions/engineering.js +1128 -0
- package/dist/browser/modules/formula/functions/financial.d.ts +202 -0
- package/dist/browser/modules/formula/functions/financial.js +2296 -0
- package/dist/browser/modules/formula/functions/lookup.d.ts +18 -0
- package/dist/browser/modules/formula/functions/lookup.js +886 -0
- package/dist/browser/modules/formula/functions/math.d.ts +114 -0
- package/dist/browser/modules/formula/functions/math.js +1406 -0
- package/dist/browser/modules/formula/functions/statistical.d.ts +193 -0
- package/dist/browser/modules/formula/functions/statistical.js +3390 -0
- package/dist/browser/modules/formula/functions/text.d.ts +86 -0
- package/dist/browser/modules/formula/functions/text.js +1845 -0
- package/dist/browser/modules/formula/host-registry.d.ts +53 -0
- package/dist/browser/modules/formula/host-registry.js +69 -0
- package/dist/browser/modules/formula/index.d.ts +39 -0
- package/dist/browser/modules/formula/index.js +49 -0
- package/dist/browser/modules/formula/install.d.ts +62 -0
- package/dist/browser/modules/formula/install.js +88 -0
- package/dist/browser/modules/formula/integration/apply-writeback-plan.d.ts +26 -0
- package/dist/browser/modules/formula/integration/apply-writeback-plan.js +210 -0
- package/dist/browser/modules/formula/integration/calculate-formulas-impl.d.ts +30 -0
- package/dist/browser/modules/formula/integration/calculate-formulas-impl.js +616 -0
- package/dist/browser/modules/formula/integration/calculate-formulas.d.ts +67 -0
- package/dist/browser/modules/formula/integration/calculate-formulas.js +68 -0
- package/dist/browser/modules/formula/integration/formula-instance.d.ts +64 -0
- package/dist/browser/modules/formula/integration/formula-instance.js +79 -0
- package/dist/browser/modules/formula/integration/workbook-adapter.d.ts +26 -0
- package/dist/browser/modules/formula/integration/workbook-adapter.js +324 -0
- package/dist/browser/modules/formula/integration/workbook-snapshot.d.ts +267 -0
- package/dist/browser/modules/formula/integration/workbook-snapshot.js +77 -0
- package/dist/browser/modules/formula/materialize/build-writeback-plan.d.ts +34 -0
- package/dist/browser/modules/formula/materialize/build-writeback-plan.js +473 -0
- package/dist/browser/modules/formula/materialize/spill-engine.d.ts +9 -0
- package/dist/browser/modules/formula/materialize/spill-engine.js +38 -0
- package/dist/browser/modules/formula/materialize/types.d.ts +179 -0
- package/dist/browser/modules/formula/materialize/types.js +29 -0
- package/dist/browser/modules/formula/materialize/writeback-plan.d.ts +167 -0
- package/dist/browser/modules/formula/materialize/writeback-plan.js +27 -0
- package/dist/browser/modules/formula/runtime/evaluator.d.ts +151 -0
- package/dist/browser/modules/formula/runtime/evaluator.js +2291 -0
- package/dist/browser/modules/formula/runtime/function-registry.d.ts +47 -0
- package/dist/browser/modules/formula/runtime/function-registry.js +840 -0
- package/dist/browser/modules/formula/runtime/values.d.ts +211 -0
- package/dist/browser/modules/formula/runtime/values.js +385 -0
- package/dist/browser/modules/formula/syntax/ast.d.ts +129 -0
- package/dist/browser/modules/formula/syntax/ast.js +28 -0
- package/dist/browser/modules/formula/syntax/parser.d.ts +18 -0
- package/dist/browser/modules/formula/syntax/parser.js +439 -0
- package/dist/browser/modules/formula/syntax/token-types.d.ts +153 -0
- package/dist/browser/modules/formula/syntax/token-types.js +59 -0
- package/dist/browser/modules/formula/syntax/tokenizer.d.ts +10 -0
- package/dist/browser/modules/formula/syntax/tokenizer.js +1074 -0
- package/dist/browser/modules/pdf/excel-bridge.js +9 -0
- package/dist/cjs/index.js +4 -0
- package/dist/cjs/modules/excel/cell.js +170 -22
- package/dist/cjs/modules/excel/defined-names.js +411 -21
- package/dist/cjs/modules/excel/image.js +24 -1
- package/dist/cjs/modules/excel/stream/workbook-reader.browser.js +14 -0
- package/dist/cjs/modules/excel/stream/workbook-writer.browser.js +48 -1
- package/dist/cjs/modules/excel/stream/worksheet-reader.js +17 -1
- package/dist/cjs/modules/excel/stream/worksheet-writer.js +45 -5
- package/dist/cjs/modules/excel/table.js +15 -2
- package/dist/cjs/modules/excel/utils/col-cache.js +15 -0
- package/dist/cjs/modules/excel/utils/drawing-utils.js +4 -0
- package/dist/cjs/modules/excel/utils/external-link-formula.js +212 -0
- package/dist/cjs/modules/excel/utils/iterate-stream.js +3 -1
- package/dist/cjs/modules/excel/utils/ooxml-paths.js +42 -2
- package/dist/cjs/modules/excel/utils/shared-strings.js +21 -2
- package/dist/cjs/modules/excel/utils/workbook-protection.js +33 -0
- package/dist/cjs/modules/excel/workbook.browser.js +318 -34
- package/dist/cjs/modules/excel/worksheet.js +20 -1
- package/dist/cjs/modules/excel/xlsx/rel-type.js +16 -1
- package/dist/cjs/modules/excel/xlsx/xform/book/defined-name-xform.js +21 -86
- package/dist/cjs/modules/excel/xlsx/xform/book/external-link-xform.js +333 -0
- package/dist/cjs/modules/excel/xlsx/xform/book/external-reference-xform.js +27 -0
- package/dist/cjs/modules/excel/xlsx/xform/book/workbook-calc-properties-xform.js +11 -2
- package/dist/cjs/modules/excel/xlsx/xform/book/workbook-protection-xform.js +69 -0
- package/dist/cjs/modules/excel/xlsx/xform/book/workbook-xform.js +38 -5
- package/dist/cjs/modules/excel/xlsx/xform/core/content-types-xform.js +18 -0
- package/dist/cjs/modules/excel/xlsx/xform/core/metadata-xform.js +161 -0
- package/dist/cjs/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +108 -0
- package/dist/cjs/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +3 -0
- package/dist/cjs/modules/excel/xlsx/xform/drawing/drawing-xform.js +10 -2
- package/dist/cjs/modules/excel/xlsx/xform/sheet/cell-xform.js +166 -8
- package/dist/cjs/modules/excel/xlsx/xform/sheet/data-validations-xform.js +1 -1
- package/dist/cjs/modules/excel/xlsx/xform/sheet/ignored-errors-xform.js +83 -0
- package/dist/cjs/modules/excel/xlsx/xform/sheet/worksheet-xform.js +9 -4
- package/dist/cjs/modules/excel/xlsx/xform/style/border-xform.js +4 -1
- package/dist/cjs/modules/excel/xlsx/xlsx.browser.js +408 -18
- package/dist/cjs/modules/excel/xlsx/xlsx.js +4 -5
- package/dist/cjs/modules/formula/compile/address-utils.js +89 -0
- package/dist/cjs/modules/formula/compile/binder.js +489 -0
- package/dist/cjs/modules/formula/compile/bound-ast.js +68 -0
- package/dist/cjs/modules/formula/compile/compiled-formula.js +387 -0
- package/dist/cjs/modules/formula/compile/dependency-analysis.js +437 -0
- package/dist/cjs/modules/formula/compile/structured-ref-utils.js +141 -0
- package/dist/cjs/modules/formula/default-syntax-probe.js +87 -0
- package/dist/cjs/modules/formula/functions/_date-context.js +33 -0
- package/dist/cjs/modules/formula/functions/_shared.js +396 -0
- package/dist/cjs/modules/formula/functions/conditional.js +354 -0
- package/dist/cjs/modules/formula/functions/database.js +288 -0
- package/dist/cjs/modules/formula/functions/date.js +883 -0
- package/dist/cjs/modules/formula/functions/dynamic-array.js +881 -0
- package/dist/cjs/modules/formula/functions/engineering.js +1183 -0
- package/dist/cjs/modules/formula/functions/financial.js +2348 -0
- package/dist/cjs/modules/formula/functions/lookup.js +902 -0
- package/dist/cjs/modules/formula/functions/math.js +1487 -0
- package/dist/cjs/modules/formula/functions/statistical.js +3488 -0
- package/dist/cjs/modules/formula/functions/text.js +1889 -0
- package/dist/cjs/modules/formula/host-registry.js +75 -0
- package/dist/cjs/modules/formula/index.js +58 -0
- package/dist/cjs/modules/formula/install.js +93 -0
- package/dist/cjs/modules/formula/integration/apply-writeback-plan.js +213 -0
- package/dist/cjs/modules/formula/integration/calculate-formulas-impl.js +619 -0
- package/dist/cjs/modules/formula/integration/calculate-formulas.js +71 -0
- package/dist/cjs/modules/formula/integration/formula-instance.js +82 -0
- package/dist/cjs/modules/formula/integration/workbook-adapter.js +327 -0
- package/dist/cjs/modules/formula/integration/workbook-snapshot.js +84 -0
- package/dist/cjs/modules/formula/materialize/build-writeback-plan.js +475 -0
- package/dist/cjs/modules/formula/materialize/spill-engine.js +42 -0
- package/dist/cjs/modules/formula/materialize/types.js +32 -0
- package/dist/cjs/modules/formula/materialize/writeback-plan.js +28 -0
- package/dist/cjs/modules/formula/runtime/evaluator.js +2298 -0
- package/dist/cjs/modules/formula/runtime/function-registry.js +846 -0
- package/dist/cjs/modules/formula/runtime/values.js +385 -0
- package/dist/cjs/modules/formula/syntax/ast.js +8 -0
- package/dist/cjs/modules/formula/syntax/parser.js +440 -0
- package/dist/cjs/modules/formula/syntax/token-types.js +32 -0
- package/dist/cjs/modules/formula/syntax/tokenizer.js +1076 -0
- package/dist/cjs/modules/pdf/excel-bridge.js +9 -0
- package/dist/esm/index.browser.js +4 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/modules/excel/cell.js +170 -22
- package/dist/esm/modules/excel/defined-names.js +411 -21
- package/dist/esm/modules/excel/image.js +24 -1
- package/dist/esm/modules/excel/stream/workbook-reader.browser.js +14 -0
- package/dist/esm/modules/excel/stream/workbook-writer.browser.js +48 -1
- package/dist/esm/modules/excel/stream/worksheet-reader.js +17 -1
- package/dist/esm/modules/excel/stream/worksheet-writer.js +45 -5
- package/dist/esm/modules/excel/table.js +15 -2
- package/dist/esm/modules/excel/utils/col-cache.js +15 -0
- package/dist/esm/modules/excel/utils/drawing-utils.js +4 -0
- package/dist/esm/modules/excel/utils/external-link-formula.js +208 -0
- package/dist/esm/modules/excel/utils/iterate-stream.js +3 -1
- package/dist/esm/modules/excel/utils/ooxml-paths.js +37 -2
- package/dist/esm/modules/excel/utils/shared-strings.js +21 -2
- package/dist/esm/modules/excel/utils/workbook-protection.js +30 -0
- package/dist/esm/modules/excel/workbook.browser.js +318 -34
- package/dist/esm/modules/excel/worksheet.js +21 -2
- package/dist/esm/modules/excel/xlsx/rel-type.js +16 -1
- package/dist/esm/modules/excel/xlsx/xform/book/defined-name-xform.js +21 -86
- package/dist/esm/modules/excel/xlsx/xform/book/external-link-xform.js +330 -0
- package/dist/esm/modules/excel/xlsx/xform/book/external-reference-xform.js +24 -0
- package/dist/esm/modules/excel/xlsx/xform/book/workbook-calc-properties-xform.js +11 -2
- package/dist/esm/modules/excel/xlsx/xform/book/workbook-protection-xform.js +66 -0
- package/dist/esm/modules/excel/xlsx/xform/book/workbook-xform.js +38 -5
- package/dist/esm/modules/excel/xlsx/xform/core/content-types-xform.js +19 -1
- package/dist/esm/modules/excel/xlsx/xform/core/metadata-xform.js +158 -0
- package/dist/esm/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +105 -0
- package/dist/esm/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +3 -0
- package/dist/esm/modules/excel/xlsx/xform/drawing/drawing-xform.js +10 -2
- package/dist/esm/modules/excel/xlsx/xform/sheet/cell-xform.js +166 -8
- package/dist/esm/modules/excel/xlsx/xform/sheet/data-validations-xform.js +1 -1
- package/dist/esm/modules/excel/xlsx/xform/sheet/ignored-errors-xform.js +80 -0
- package/dist/esm/modules/excel/xlsx/xform/sheet/worksheet-xform.js +9 -4
- package/dist/esm/modules/excel/xlsx/xform/style/border-xform.js +4 -1
- package/dist/esm/modules/excel/xlsx/xlsx.browser.js +410 -20
- package/dist/esm/modules/excel/xlsx/xlsx.js +4 -5
- package/dist/esm/modules/formula/compile/address-utils.js +83 -0
- package/dist/esm/modules/formula/compile/binder.js +487 -0
- package/dist/esm/modules/formula/compile/bound-ast.js +80 -0
- package/dist/esm/modules/formula/compile/compiled-formula.js +383 -0
- package/dist/esm/modules/formula/compile/dependency-analysis.js +432 -0
- package/dist/esm/modules/formula/compile/structured-ref-utils.js +136 -0
- package/dist/esm/modules/formula/default-syntax-probe.js +83 -0
- package/dist/esm/modules/formula/functions/_date-context.js +29 -0
- package/dist/esm/modules/formula/functions/_shared.js +381 -0
- package/dist/esm/modules/formula/functions/conditional.js +343 -0
- package/dist/esm/modules/formula/functions/database.js +274 -0
- package/dist/esm/modules/formula/functions/date.js +855 -0
- package/dist/esm/modules/formula/functions/dynamic-array.js +860 -0
- package/dist/esm/modules/formula/functions/engineering.js +1128 -0
- package/dist/esm/modules/formula/functions/financial.js +2296 -0
- package/dist/esm/modules/formula/functions/lookup.js +886 -0
- package/dist/esm/modules/formula/functions/math.js +1406 -0
- package/dist/esm/modules/formula/functions/statistical.js +3390 -0
- package/dist/esm/modules/formula/functions/text.js +1845 -0
- package/dist/esm/modules/formula/host-registry.js +69 -0
- package/dist/esm/modules/formula/index.js +49 -0
- package/dist/esm/modules/formula/install.js +88 -0
- package/dist/esm/modules/formula/integration/apply-writeback-plan.js +210 -0
- package/dist/esm/modules/formula/integration/calculate-formulas-impl.js +616 -0
- package/dist/esm/modules/formula/integration/calculate-formulas.js +68 -0
- package/dist/esm/modules/formula/integration/formula-instance.js +79 -0
- package/dist/esm/modules/formula/integration/workbook-adapter.js +324 -0
- package/dist/esm/modules/formula/integration/workbook-snapshot.js +77 -0
- package/dist/esm/modules/formula/materialize/build-writeback-plan.js +473 -0
- package/dist/esm/modules/formula/materialize/spill-engine.js +38 -0
- package/dist/esm/modules/formula/materialize/types.js +29 -0
- package/dist/esm/modules/formula/materialize/writeback-plan.js +27 -0
- package/dist/esm/modules/formula/runtime/evaluator.js +2291 -0
- package/dist/esm/modules/formula/runtime/function-registry.js +840 -0
- package/dist/esm/modules/formula/runtime/values.js +385 -0
- package/dist/esm/modules/formula/syntax/ast.js +28 -0
- package/dist/esm/modules/formula/syntax/parser.js +439 -0
- package/dist/esm/modules/formula/syntax/token-types.js +59 -0
- package/dist/esm/modules/formula/syntax/tokenizer.js +1074 -0
- package/dist/esm/modules/pdf/excel-bridge.js +9 -0
- package/dist/iife/excelts.iife.js +2302 -373
- package/dist/iife/excelts.iife.js.map +1 -1
- package/dist/iife/excelts.iife.min.js +34 -34
- package/dist/types/index.browser.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/modules/excel/cell.d.ts +17 -3
- package/dist/types/modules/excel/defined-names.d.ts +96 -1
- package/dist/types/modules/excel/image.d.ts +11 -0
- package/dist/types/modules/excel/stream/workbook-reader.browser.d.ts +9 -3
- package/dist/types/modules/excel/stream/workbook-reader.d.ts +2 -1
- package/dist/types/modules/excel/stream/workbook-writer.browser.d.ts +39 -5
- package/dist/types/modules/excel/stream/workbook-writer.d.ts +3 -2
- package/dist/types/modules/excel/stream/worksheet-writer.d.ts +39 -6
- package/dist/types/modules/excel/types.d.ts +133 -2
- package/dist/types/modules/excel/utils/col-cache.d.ts +1 -0
- package/dist/types/modules/excel/utils/drawing-utils.d.ts +3 -3
- package/dist/types/modules/excel/utils/external-link-formula.d.ts +76 -0
- package/dist/types/modules/excel/utils/iterate-stream.d.ts +9 -3
- package/dist/types/modules/excel/utils/ooxml-paths.d.ts +19 -0
- package/dist/types/modules/excel/utils/shared-strings.d.ts +8 -3
- package/dist/types/modules/excel/utils/workbook-protection.d.ts +30 -0
- package/dist/types/modules/excel/workbook.browser.d.ts +257 -6
- package/dist/types/modules/excel/workbook.d.ts +1 -1
- package/dist/types/modules/excel/worksheet.d.ts +3 -1
- package/dist/types/modules/excel/xlsx/rel-type.d.ts +15 -0
- package/dist/types/modules/excel/xlsx/xform/book/defined-name-xform.d.ts +6 -5
- package/dist/types/modules/excel/xlsx/xform/book/external-link-xform.d.ts +84 -0
- package/dist/types/modules/excel/xlsx/xform/book/external-reference-xform.d.ts +17 -0
- package/dist/types/modules/excel/xlsx/xform/book/workbook-calc-properties-xform.d.ts +3 -0
- package/dist/types/modules/excel/xlsx/xform/book/workbook-protection-xform.d.ts +20 -0
- package/dist/types/modules/excel/xlsx/xform/core/metadata-xform.d.ts +56 -0
- package/dist/types/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.d.ts +26 -0
- package/dist/types/modules/excel/xlsx/xform/sheet/cell-xform.d.ts +1 -1
- package/dist/types/modules/excel/xlsx/xform/sheet/ignored-errors-xform.d.ts +21 -0
- package/dist/types/modules/excel/xlsx/xlsx.browser.d.ts +172 -13
- package/dist/types/modules/excel/xlsx/xlsx.d.ts +7 -4
- package/dist/types/modules/formula/compile/address-utils.d.ts +62 -0
- package/dist/types/modules/formula/compile/binder.d.ts +42 -0
- package/dist/types/modules/formula/compile/bound-ast.d.ts +230 -0
- package/dist/types/modules/formula/compile/compiled-formula.d.ts +137 -0
- package/dist/types/modules/formula/compile/dependency-analysis.d.ts +93 -0
- package/dist/types/modules/formula/compile/structured-ref-utils.d.ts +93 -0
- package/dist/types/modules/formula/default-syntax-probe.d.ts +79 -0
- package/dist/types/modules/formula/functions/_date-context.d.ts +4 -0
- package/dist/types/modules/formula/functions/_shared.d.ts +121 -0
- package/dist/types/modules/formula/functions/conditional.d.ts +27 -0
- package/dist/types/modules/formula/functions/database.d.ts +37 -0
- package/dist/types/modules/formula/functions/date.d.ts +61 -0
- package/dist/types/modules/formula/functions/dynamic-array.d.ts +23 -0
- package/dist/types/modules/formula/functions/engineering.d.ts +57 -0
- package/dist/types/modules/formula/functions/financial.d.ts +202 -0
- package/dist/types/modules/formula/functions/lookup.d.ts +18 -0
- package/dist/types/modules/formula/functions/math.d.ts +114 -0
- package/dist/types/modules/formula/functions/statistical.d.ts +193 -0
- package/dist/types/modules/formula/functions/text.d.ts +86 -0
- package/dist/types/modules/formula/host-registry.d.ts +53 -0
- package/dist/types/modules/formula/index.d.ts +39 -0
- package/dist/types/modules/formula/install.d.ts +62 -0
- package/dist/types/modules/formula/integration/apply-writeback-plan.d.ts +26 -0
- package/dist/types/modules/formula/integration/calculate-formulas-impl.d.ts +30 -0
- package/dist/types/modules/formula/integration/calculate-formulas.d.ts +67 -0
- package/dist/types/modules/formula/integration/formula-instance.d.ts +64 -0
- package/dist/types/modules/formula/integration/workbook-adapter.d.ts +26 -0
- package/dist/types/modules/formula/integration/workbook-snapshot.d.ts +267 -0
- package/dist/types/modules/formula/materialize/build-writeback-plan.d.ts +34 -0
- package/dist/types/modules/formula/materialize/spill-engine.d.ts +9 -0
- package/dist/types/modules/formula/materialize/types.d.ts +179 -0
- package/dist/types/modules/formula/materialize/writeback-plan.d.ts +167 -0
- package/dist/types/modules/formula/runtime/evaluator.d.ts +151 -0
- package/dist/types/modules/formula/runtime/function-registry.d.ts +47 -0
- package/dist/types/modules/formula/runtime/values.d.ts +211 -0
- package/dist/types/modules/formula/syntax/ast.d.ts +129 -0
- package/dist/types/modules/formula/syntax/parser.d.ts +18 -0
- package/dist/types/modules/formula/syntax/token-types.d.ts +153 -0
- package/dist/types/modules/formula/syntax/tokenizer.d.ts +10 -0
- package/package.json +28 -28
|
@@ -0,0 +1,2298 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Evaluator — Execute BoundExpr using the RuntimeValue system.
|
|
4
|
+
*
|
|
5
|
+
* The evaluator operates on BoundExpr (from the compile phase),
|
|
6
|
+
* WorkbookSnapshot (from the snapshot phase), and RuntimeValue
|
|
7
|
+
* (the value system).
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.EvalSession = void 0;
|
|
11
|
+
exports.evaluate = evaluate;
|
|
12
|
+
exports.evaluateFormula = evaluateFormula;
|
|
13
|
+
exports.evaluateFormulaRaw = evaluateFormulaRaw;
|
|
14
|
+
exports.implicitIntersect = implicitIntersect;
|
|
15
|
+
const address_utils_1 = require("../compile/address-utils");
|
|
16
|
+
const binder_1 = require("../compile/binder");
|
|
17
|
+
const structured_ref_utils_1 = require("../compile/structured-ref-utils");
|
|
18
|
+
const workbook_snapshot_1 = require("../integration/workbook-snapshot");
|
|
19
|
+
const parser_1 = require("../syntax/parser");
|
|
20
|
+
const token_types_1 = require("../syntax/token-types");
|
|
21
|
+
const tokenizer_1 = require("../syntax/tokenizer");
|
|
22
|
+
const function_registry_1 = require("./function-registry");
|
|
23
|
+
const values_1 = require("./values");
|
|
24
|
+
/**
|
|
25
|
+
* Per-calculation mutable state.
|
|
26
|
+
*/
|
|
27
|
+
class EvalSession {
|
|
28
|
+
constructor() {
|
|
29
|
+
/** Cells currently on the evaluation call stack (circular-ref detection). */
|
|
30
|
+
this.evaluating = new Set();
|
|
31
|
+
/**
|
|
32
|
+
* Unified formula result cache.
|
|
33
|
+
* Each entry holds both the scalar form (for dependents) and the raw form
|
|
34
|
+
* (for materialize). This replaces the previous separate cache/rawCache
|
|
35
|
+
* pattern with a single, self-documenting structure.
|
|
36
|
+
*/
|
|
37
|
+
this.resultCache = new Map();
|
|
38
|
+
/** Cache for runtime name resolution (defined names that need parsing). */
|
|
39
|
+
this.nameCache = new Map();
|
|
40
|
+
/** Fallback values for circular references during iterative calculation. */
|
|
41
|
+
this.circularFallback = new Map();
|
|
42
|
+
/**
|
|
43
|
+
* Live spill map: cell key → (masterKey, row-offset, col-offset).
|
|
44
|
+
*
|
|
45
|
+
* Populated as soon as a dynamic-array formula is evaluated and yields
|
|
46
|
+
* an array result. Downstream formulas that read a cell inside the
|
|
47
|
+
* spill region look the master's cached array up via this map and
|
|
48
|
+
* return the correct element — even before materialize has written
|
|
49
|
+
* the ghost cells to the snapshot.
|
|
50
|
+
*
|
|
51
|
+
* This is the fix for "first-pass `=SUM(A1:A5)` over a `=SEQUENCE(5)`
|
|
52
|
+
* spill" — without the live map, `getCellValue("S", 2, 1)` returned
|
|
53
|
+
* BLANK and SUM only counted the master cell.
|
|
54
|
+
*/
|
|
55
|
+
this.liveSpills = new Map();
|
|
56
|
+
/**
|
|
57
|
+
* Runtime dependency recorder — tracks cell accesses made during evaluation.
|
|
58
|
+
*
|
|
59
|
+
* When a formula with `hasDynamicRefs` (INDIRECT/OFFSET) is being evaluated,
|
|
60
|
+
* every `getCellValue` / `buildRangeArray` call records the accessed cell/range
|
|
61
|
+
* key here. After evaluation, these dynamic edges can be merged with the
|
|
62
|
+
* compiled static dependency set to produce a complete dependency graph.
|
|
63
|
+
*
|
|
64
|
+
* Key: formula cell key being evaluated → Set of accessed cell keys.
|
|
65
|
+
* Only populated for formulas that have `hasDynamicRefs === true`.
|
|
66
|
+
*/
|
|
67
|
+
this.dynamicDeps = new Map();
|
|
68
|
+
/**
|
|
69
|
+
* The formula cell key currently being recorded (null if recording is off).
|
|
70
|
+
* Set before evaluating a formula with dynamic refs, cleared after.
|
|
71
|
+
*/
|
|
72
|
+
this.recordingKey = null;
|
|
73
|
+
/**
|
|
74
|
+
* Current LAMBDA invocation depth. Guards against unbounded recursion
|
|
75
|
+
* (e.g. `LAMBDA(x, x(x))(LAMBDA(x, x(x)))`) that would otherwise overflow
|
|
76
|
+
* the JS call stack. Excel documents a recursion limit of ~256.
|
|
77
|
+
*/
|
|
78
|
+
this.lambdaDepth = 0;
|
|
79
|
+
/**
|
|
80
|
+
* AST cache for INDIRECT re-parsing. INDIRECT receives a runtime string
|
|
81
|
+
* describing a reference; re-parsing it per invocation would be wasted
|
|
82
|
+
* work, so we memoise the `bound` expression keyed on the reference
|
|
83
|
+
* text. This belongs to the session (per-calculation lifetime) rather
|
|
84
|
+
* than the snapshot because the bindings depend on the evaluation
|
|
85
|
+
* context.
|
|
86
|
+
*/
|
|
87
|
+
this.indirectAstCache = new Map();
|
|
88
|
+
}
|
|
89
|
+
makeKey(sheet, row, col) {
|
|
90
|
+
return (0, workbook_snapshot_1.formulaCellKey)(sheet, row, col);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Record a cell access for the currently-recording formula.
|
|
94
|
+
*/
|
|
95
|
+
recordAccess(sheet, row, col) {
|
|
96
|
+
if (this.recordingKey === null) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
let deps = this.dynamicDeps.get(this.recordingKey);
|
|
100
|
+
if (!deps) {
|
|
101
|
+
deps = new Set();
|
|
102
|
+
this.dynamicDeps.set(this.recordingKey, deps);
|
|
103
|
+
}
|
|
104
|
+
deps.add((0, workbook_snapshot_1.formulaCellKey)(sheet, row, col));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
exports.EvalSession = EvalSession;
|
|
108
|
+
// ============================================================================
|
|
109
|
+
// Main Evaluate Function
|
|
110
|
+
// ============================================================================
|
|
111
|
+
/**
|
|
112
|
+
* Exhaustiveness helper — TypeScript narrows `never` here to prove that
|
|
113
|
+
* every discriminated-union variant was handled. At runtime this should be
|
|
114
|
+
* unreachable; if a new variant is added without a case, compilation fails.
|
|
115
|
+
*/
|
|
116
|
+
function assertNever(x) {
|
|
117
|
+
throw new Error(`unexpected variant: ${JSON.stringify(x)}`);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Evaluate a BoundExpr to produce a RuntimeValue.
|
|
121
|
+
*/
|
|
122
|
+
function evaluate(expr, ctx, session) {
|
|
123
|
+
switch (expr.kind) {
|
|
124
|
+
case 1 /* BoundExprKind.Literal */:
|
|
125
|
+
return evaluateLiteral(expr);
|
|
126
|
+
case 2 /* BoundExprKind.CellRef */:
|
|
127
|
+
return evaluateCellRef(expr, ctx, session);
|
|
128
|
+
case 3 /* BoundExprKind.AreaRef */:
|
|
129
|
+
return evaluateAreaRef(expr, ctx, session);
|
|
130
|
+
case 4 /* BoundExprKind.ColRangeRef */:
|
|
131
|
+
return evaluateColRange(expr, ctx, session);
|
|
132
|
+
case 5 /* BoundExprKind.RowRangeRef */:
|
|
133
|
+
return evaluateRowRange(expr, ctx, session);
|
|
134
|
+
case 6 /* BoundExprKind.Ref3D */:
|
|
135
|
+
return evaluateRef3D(expr, ctx, session);
|
|
136
|
+
case 7 /* BoundExprKind.BinaryOp */:
|
|
137
|
+
return evaluateBinaryOp(expr.op, expr.left, expr.right, ctx, session);
|
|
138
|
+
case 8 /* BoundExprKind.UnaryOp */:
|
|
139
|
+
return evaluateUnaryOp(expr.op, expr.operand, ctx, session);
|
|
140
|
+
case 9 /* BoundExprKind.Percent */:
|
|
141
|
+
return evaluatePercent(expr.operand, ctx, session);
|
|
142
|
+
case 10 /* BoundExprKind.Call */:
|
|
143
|
+
return evaluateCall(expr, ctx, session);
|
|
144
|
+
case 11 /* BoundExprKind.SpecialCall */:
|
|
145
|
+
return evaluateSpecialCall(expr, ctx, session);
|
|
146
|
+
case 12 /* BoundExprKind.Array */:
|
|
147
|
+
return evaluateArrayLiteral(expr, ctx, session);
|
|
148
|
+
case 13 /* BoundExprKind.NameExpr */:
|
|
149
|
+
return evaluateNameExpr(expr, ctx, session);
|
|
150
|
+
case 14 /* BoundExprKind.Lambda */:
|
|
151
|
+
return evaluateLambdaExpr(expr, ctx);
|
|
152
|
+
case 15 /* BoundExprKind.StructuredRef */:
|
|
153
|
+
return evaluateStructuredRef(expr, ctx, session);
|
|
154
|
+
default:
|
|
155
|
+
return assertNever(expr);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// ============================================================================
|
|
159
|
+
// Literal
|
|
160
|
+
// ============================================================================
|
|
161
|
+
function evaluateLiteral(expr) {
|
|
162
|
+
if (expr.errorCode) {
|
|
163
|
+
return (0, values_1.rvError)(expr.errorCode);
|
|
164
|
+
}
|
|
165
|
+
if (expr.value === null) {
|
|
166
|
+
return values_1.BLANK;
|
|
167
|
+
}
|
|
168
|
+
if (typeof expr.value === "number") {
|
|
169
|
+
return (0, values_1.rvNumber)(expr.value);
|
|
170
|
+
}
|
|
171
|
+
if (typeof expr.value === "string") {
|
|
172
|
+
return (0, values_1.rvString)(expr.value);
|
|
173
|
+
}
|
|
174
|
+
if (typeof expr.value === "boolean") {
|
|
175
|
+
return (0, values_1.rvBoolean)(expr.value);
|
|
176
|
+
}
|
|
177
|
+
return values_1.BLANK;
|
|
178
|
+
}
|
|
179
|
+
// ============================================================================
|
|
180
|
+
// Cell Reference
|
|
181
|
+
// ============================================================================
|
|
182
|
+
function evaluateCellRef(expr, ctx, session) {
|
|
183
|
+
return (0, values_1.rvCellRef)(expr.sheet, expr.row, expr.col);
|
|
184
|
+
}
|
|
185
|
+
// ============================================================================
|
|
186
|
+
// Area Reference → ReferenceValue
|
|
187
|
+
// ============================================================================
|
|
188
|
+
function evaluateAreaRef(expr, ctx, session) {
|
|
189
|
+
return (0, values_1.rvRef)(expr.sheet, expr.top, expr.left, expr.bottom, expr.right);
|
|
190
|
+
}
|
|
191
|
+
function evaluateColRange(expr, ctx, session) {
|
|
192
|
+
const ws = ctx.snapshot.worksheetsByName.get(expr.sheet.toLowerCase());
|
|
193
|
+
if (!ws || !ws.dimensions) {
|
|
194
|
+
return (0, values_1.rvArray)([]);
|
|
195
|
+
}
|
|
196
|
+
return (0, values_1.rvRef)(expr.sheet, ws.dimensions.top, expr.leftCol, ws.dimensions.bottom, expr.rightCol);
|
|
197
|
+
}
|
|
198
|
+
function evaluateRowRange(expr, ctx, session) {
|
|
199
|
+
const ws = ctx.snapshot.worksheetsByName.get(expr.sheet.toLowerCase());
|
|
200
|
+
if (!ws || !ws.dimensions) {
|
|
201
|
+
return (0, values_1.rvArray)([]);
|
|
202
|
+
}
|
|
203
|
+
return (0, values_1.rvRef)(expr.sheet, expr.topRow, ws.dimensions.left, expr.bottomRow, ws.dimensions.right);
|
|
204
|
+
}
|
|
205
|
+
function evaluateRef3D(expr, ctx, session) {
|
|
206
|
+
const areas = [];
|
|
207
|
+
for (const sheet of expr.sheets) {
|
|
208
|
+
if (expr.inner.kind === 2 /* BoundExprKind.CellRef */) {
|
|
209
|
+
areas.push({
|
|
210
|
+
sheet,
|
|
211
|
+
top: expr.inner.row,
|
|
212
|
+
left: expr.inner.col,
|
|
213
|
+
bottom: expr.inner.row,
|
|
214
|
+
right: expr.inner.col
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
// For AreaRef: if the inner range is a whole-column or whole-row
|
|
219
|
+
// reference (top = 1 & bottom = 1048576, or left = 1 & right =
|
|
220
|
+
// 16384), clamp it against each sheet's actual used dimensions.
|
|
221
|
+
// Without this clamp, a 3D reference like `Sheet1:Sheet3!A:A`
|
|
222
|
+
// would allocate 3 × 1M rows of BLANK values and spend seconds
|
|
223
|
+
// (or OOM) before SUM even starts. The non-3D path already does
|
|
224
|
+
// this clamp in `evaluateColRange`; parity with that is what we
|
|
225
|
+
// want here.
|
|
226
|
+
let top = expr.inner.top;
|
|
227
|
+
let left = expr.inner.left;
|
|
228
|
+
let bottom = expr.inner.bottom;
|
|
229
|
+
let right = expr.inner.right;
|
|
230
|
+
const isWholeCol = top === 1 && bottom === 1048576;
|
|
231
|
+
const isWholeRow = left === 1 && right === 16384;
|
|
232
|
+
if (isWholeCol || isWholeRow) {
|
|
233
|
+
const ws = ctx.snapshot.worksheetsByName.get(sheet.toLowerCase());
|
|
234
|
+
const dims = ws?.dimensions;
|
|
235
|
+
if (dims) {
|
|
236
|
+
if (isWholeCol) {
|
|
237
|
+
top = dims.top;
|
|
238
|
+
bottom = dims.bottom;
|
|
239
|
+
}
|
|
240
|
+
if (isWholeRow) {
|
|
241
|
+
left = dims.left;
|
|
242
|
+
right = dims.right;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
areas.push({ sheet, top, left, bottom, right });
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return { kind: 6 /* RVKind.Reference */, areas };
|
|
250
|
+
}
|
|
251
|
+
// ============================================================================
|
|
252
|
+
// Build Range Array from Snapshot
|
|
253
|
+
// ============================================================================
|
|
254
|
+
function buildRangeArray(ctx, session, sheet, top, left, bottom, right) {
|
|
255
|
+
// Hoist the worksheet lookup once for the entire range — avoids
|
|
256
|
+
// N redundant toLowerCase()/Map.get() calls inside the hot loop.
|
|
257
|
+
const ws = ctx.snapshot.worksheetsByName.get(sheet.toLowerCase());
|
|
258
|
+
const cells = ws?.cells;
|
|
259
|
+
const wsHiddenRows = ws?.hiddenRows;
|
|
260
|
+
const compiledFormulas = ctx.compiledFormulas;
|
|
261
|
+
const resultCache = session.resultCache;
|
|
262
|
+
// Hoist the recording guard — when not recording, skip recordAccess
|
|
263
|
+
// entirely in the loop instead of paying the function-call overhead.
|
|
264
|
+
const recording = session.recordingKey !== null;
|
|
265
|
+
const rows = [];
|
|
266
|
+
// Lazily-allocated masks: only materialized once we encounter a
|
|
267
|
+
// SUBTOTAL/AGGREGATE cell or a hidden row inside the range. For the
|
|
268
|
+
// common case (plain data range, no hidden rows) we never touch these
|
|
269
|
+
// and emit an ArrayValue without extra metadata.
|
|
270
|
+
let subtotalMask;
|
|
271
|
+
let hiddenRowMask;
|
|
272
|
+
const height = bottom - top + 1;
|
|
273
|
+
const width = right - left + 1;
|
|
274
|
+
for (let r = top; r <= bottom; r++) {
|
|
275
|
+
const row = [];
|
|
276
|
+
const ri = r - top;
|
|
277
|
+
// Record row visibility — SUBTOTAL 1xx / AGGREGATE opt 5/7 use it.
|
|
278
|
+
if (wsHiddenRows?.has(r)) {
|
|
279
|
+
if (!hiddenRowMask) {
|
|
280
|
+
hiddenRowMask = new Array(height).fill(false);
|
|
281
|
+
}
|
|
282
|
+
hiddenRowMask[ri] = true;
|
|
283
|
+
}
|
|
284
|
+
for (let c = left; c <= right; c++) {
|
|
285
|
+
if (recording) {
|
|
286
|
+
session.recordAccess(sheet, r, c);
|
|
287
|
+
}
|
|
288
|
+
// Missing worksheet: matches getCellValue's BLANK fallback.
|
|
289
|
+
if (!cells) {
|
|
290
|
+
row.push(values_1.BLANK);
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
const cell = cells.get((0, workbook_snapshot_1.snapshotCellKey)(r, c));
|
|
294
|
+
if (!cell) {
|
|
295
|
+
// No snapshot cell yet — might still be inside a live spill
|
|
296
|
+
// (e.g. reading A2..A5 while A1 = SEQUENCE(5) is still being
|
|
297
|
+
// materialized). See `readLiveSpill` for the lookup path.
|
|
298
|
+
const live = readLiveSpill(sheet, r, c, session);
|
|
299
|
+
row.push(live ? (0, values_1.topLeft)(live) : values_1.BLANK);
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
if (cell.formulaKind !== "none" && cell.formula) {
|
|
303
|
+
const fKey = (0, workbook_snapshot_1.formulaCellKey)(sheet, r, c);
|
|
304
|
+
const compiled = compiledFormulas.get(fKey);
|
|
305
|
+
// Mark SUBTOTAL/AGGREGATE output cells so an outer SUBTOTAL /
|
|
306
|
+
// AGGREGATE over this range knows to skip them (Excel's
|
|
307
|
+
// no-double-count semantics).
|
|
308
|
+
if (compiled?.isSubtotalOutput) {
|
|
309
|
+
if (!subtotalMask) {
|
|
310
|
+
subtotalMask = new Array(height);
|
|
311
|
+
for (let i = 0; i < height; i++) {
|
|
312
|
+
subtotalMask[i] = new Array(width).fill(false);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
subtotalMask[ri][c - left] = true;
|
|
316
|
+
}
|
|
317
|
+
const cached = resultCache.get(fKey);
|
|
318
|
+
if (cached !== undefined) {
|
|
319
|
+
row.push((0, values_1.topLeft)(cached.scalar));
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
if (compiled) {
|
|
323
|
+
row.push((0, values_1.topLeft)(evaluateFormula(compiled, ctx, session)));
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
// Non-formula snapshot cell — but it might still be a ghost slot
|
|
328
|
+
// that a fresh dynamic-array spill is about to overwrite. Prefer
|
|
329
|
+
// the live value when a master is registered so this-pass SUM /
|
|
330
|
+
// LOOKUP / etc. see the new spill immediately.
|
|
331
|
+
const live = readLiveSpill(sheet, r, c, session);
|
|
332
|
+
if (live) {
|
|
333
|
+
row.push((0, values_1.topLeft)(live));
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
row.push((0, values_1.topLeft)((0, values_1.fromSnapshotValue)(cell.value)));
|
|
337
|
+
}
|
|
338
|
+
rows.push(row);
|
|
339
|
+
}
|
|
340
|
+
return (0, values_1.rvArray)(rows, top, left, subtotalMask, hiddenRowMask);
|
|
341
|
+
}
|
|
342
|
+
// ============================================================================
|
|
343
|
+
// Dereference: Reference → concrete value
|
|
344
|
+
// ============================================================================
|
|
345
|
+
/**
|
|
346
|
+
* Resolve a `ReferenceValue` to its concrete value (scalar or array).
|
|
347
|
+
* - Single-cell references (from CellRef nodes) resolve to a scalar.
|
|
348
|
+
* - Area references (even 1x1 from A1:A1) produce an array.
|
|
349
|
+
* - Multi-area references (from 3D refs) flatten all areas into one array.
|
|
350
|
+
* Non-reference values are returned unchanged.
|
|
351
|
+
*/
|
|
352
|
+
function dereferenceValue(v, ctx, session) {
|
|
353
|
+
if (v.kind !== 6 /* RVKind.Reference */) {
|
|
354
|
+
return v;
|
|
355
|
+
}
|
|
356
|
+
if (v.areas.length === 0) {
|
|
357
|
+
return values_1.BLANK;
|
|
358
|
+
}
|
|
359
|
+
// Single-cell references (from CellRef nodes) resolve to scalar
|
|
360
|
+
if (v.singleCell) {
|
|
361
|
+
const area = v.areas[0];
|
|
362
|
+
return getCellValue(area.sheet, area.top, area.left, ctx, session);
|
|
363
|
+
}
|
|
364
|
+
// Single area — build range array
|
|
365
|
+
if (v.areas.length === 1) {
|
|
366
|
+
const area = v.areas[0];
|
|
367
|
+
return buildRangeArray(ctx, session, area.sheet, area.top, area.left, area.bottom, area.right);
|
|
368
|
+
}
|
|
369
|
+
// Multi-area (3D reference) — flatten all areas into one array
|
|
370
|
+
const allRows = [];
|
|
371
|
+
let mergedSubtotal;
|
|
372
|
+
let mergedHidden;
|
|
373
|
+
for (const area of v.areas) {
|
|
374
|
+
const arr = buildRangeArray(ctx, session, area.sheet, area.top, area.left, area.bottom, area.right);
|
|
375
|
+
const startRow = allRows.length;
|
|
376
|
+
for (const row of arr.rows) {
|
|
377
|
+
allRows.push([...row]);
|
|
378
|
+
}
|
|
379
|
+
// Merge masks from this area into the flattened output. Only
|
|
380
|
+
// allocate the merged masks when the first masked area shows up —
|
|
381
|
+
// most multi-area refs have no masks and should pay no overhead.
|
|
382
|
+
if (arr.subtotalMask || arr.hiddenRowMask) {
|
|
383
|
+
const height = arr.height;
|
|
384
|
+
if (arr.subtotalMask) {
|
|
385
|
+
if (!mergedSubtotal) {
|
|
386
|
+
mergedSubtotal = [];
|
|
387
|
+
for (let i = 0; i < startRow; i++) {
|
|
388
|
+
// Widths may differ across areas; mask rows use each area's
|
|
389
|
+
// own width so an outer SUBTOTAL reads the right positions.
|
|
390
|
+
mergedSubtotal.push([]);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
for (let r = 0; r < height; r++) {
|
|
394
|
+
mergedSubtotal.push([...arr.subtotalMask[r]]);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
else if (mergedSubtotal) {
|
|
398
|
+
// Pad with empty rows so indices stay aligned.
|
|
399
|
+
for (let r = 0; r < height; r++) {
|
|
400
|
+
mergedSubtotal.push([]);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
if (arr.hiddenRowMask) {
|
|
404
|
+
if (!mergedHidden) {
|
|
405
|
+
mergedHidden = new Array(startRow).fill(false);
|
|
406
|
+
}
|
|
407
|
+
for (let r = 0; r < height; r++) {
|
|
408
|
+
mergedHidden.push(arr.hiddenRowMask[r] ?? false);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
else if (mergedHidden) {
|
|
412
|
+
for (let r = 0; r < height; r++) {
|
|
413
|
+
mergedHidden.push(false);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
else if (mergedSubtotal || mergedHidden) {
|
|
418
|
+
// An earlier area had masks; pad this area's rows with non-masked
|
|
419
|
+
// entries to keep row indices aligned.
|
|
420
|
+
const height = arr.height;
|
|
421
|
+
if (mergedSubtotal) {
|
|
422
|
+
for (let r = 0; r < height; r++) {
|
|
423
|
+
mergedSubtotal.push([]);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
if (mergedHidden) {
|
|
427
|
+
for (let r = 0; r < height; r++) {
|
|
428
|
+
mergedHidden.push(false);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
return (0, values_1.rvArray)(allRows, undefined, undefined, mergedSubtotal, mergedHidden);
|
|
434
|
+
}
|
|
435
|
+
// ============================================================================
|
|
436
|
+
// Get Cell Value from Snapshot
|
|
437
|
+
// ============================================================================
|
|
438
|
+
function getCellValue(sheetName, row, col, ctx, session) {
|
|
439
|
+
// Record this access for runtime dependency tracking. Inline guard
|
|
440
|
+
// avoids the function call overhead in the common case where no
|
|
441
|
+
// recording is active (formulas without dynamic refs).
|
|
442
|
+
if (session.recordingKey !== null) {
|
|
443
|
+
session.recordAccess(sheetName, row, col);
|
|
444
|
+
}
|
|
445
|
+
const ws = ctx.snapshot.worksheetsByName.get(sheetName.toLowerCase());
|
|
446
|
+
if (!ws) {
|
|
447
|
+
return values_1.BLANK;
|
|
448
|
+
}
|
|
449
|
+
const cellKey = (0, workbook_snapshot_1.snapshotCellKey)(row, col);
|
|
450
|
+
const cell = ws.cells.get(cellKey);
|
|
451
|
+
if (!cell) {
|
|
452
|
+
// The cell isn't in the snapshot, but we might still have a live
|
|
453
|
+
// spill value for it — look up the master formula and extract the
|
|
454
|
+
// right array element. Matters when a downstream formula like
|
|
455
|
+
// `SUM(A1:A5)` runs before materialize writes the ghost cells for
|
|
456
|
+
// `A1 = SEQUENCE(5)` into the snapshot.
|
|
457
|
+
return readLiveSpill(sheetName, row, col, session) ?? values_1.BLANK;
|
|
458
|
+
}
|
|
459
|
+
// If this cell has a formula, evaluate it
|
|
460
|
+
if (cell.formulaKind !== "none" && cell.formula) {
|
|
461
|
+
const fKey = (0, workbook_snapshot_1.formulaCellKey)(sheetName, row, col);
|
|
462
|
+
// Check cache — return scalar form for dependency resolution
|
|
463
|
+
const cached = session.resultCache.get(fKey);
|
|
464
|
+
if (cached !== undefined) {
|
|
465
|
+
return cached.scalar;
|
|
466
|
+
}
|
|
467
|
+
// Get compiled formula
|
|
468
|
+
const compiled = ctx.compiledFormulas.get(fKey);
|
|
469
|
+
if (compiled) {
|
|
470
|
+
return evaluateFormula(compiled, ctx, session);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
// Non-formula cell — but it might also be a ghost for a live spill
|
|
474
|
+
// (e.g. a value that exists in the snapshot from a previous calc
|
|
475
|
+
// cycle but is about to be overwritten by a fresh spill). Prefer the
|
|
476
|
+
// live value when a master is registered.
|
|
477
|
+
const spill = readLiveSpill(sheetName, row, col, session);
|
|
478
|
+
if (spill) {
|
|
479
|
+
return spill;
|
|
480
|
+
}
|
|
481
|
+
return (0, values_1.fromSnapshotValue)(cell.value);
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Retrieve the spill-target value at (sheetName, row, col) from an
|
|
485
|
+
* already-evaluated dynamic-array master. Returns `undefined` when no
|
|
486
|
+
* master is registered for that cell — callers fall back to the
|
|
487
|
+
* snapshot value or BLANK.
|
|
488
|
+
*/
|
|
489
|
+
function readLiveSpill(sheetName, row, col, session) {
|
|
490
|
+
const key = session.makeKey(sheetName, row, col);
|
|
491
|
+
const spill = session.liveSpills.get(key);
|
|
492
|
+
if (!spill) {
|
|
493
|
+
return undefined;
|
|
494
|
+
}
|
|
495
|
+
const master = session.resultCache.get(spill.masterKey);
|
|
496
|
+
if (!master || master.raw.kind !== 5 /* RVKind.Array */) {
|
|
497
|
+
return undefined;
|
|
498
|
+
}
|
|
499
|
+
const arr = master.raw;
|
|
500
|
+
if (spill.rowOffset >= arr.height || spill.colOffset >= arr.width) {
|
|
501
|
+
return undefined;
|
|
502
|
+
}
|
|
503
|
+
return arr.rows[spill.rowOffset][spill.colOffset];
|
|
504
|
+
}
|
|
505
|
+
// ============================================================================
|
|
506
|
+
// Evaluate a Compiled Formula
|
|
507
|
+
// ============================================================================
|
|
508
|
+
/**
|
|
509
|
+
* Shared implementation for evaluateFormula and evaluateFormulaRaw.
|
|
510
|
+
*
|
|
511
|
+
* Handles key computation, cache lookup, circular reference detection,
|
|
512
|
+
* expression evaluation, and result caching.
|
|
513
|
+
*/
|
|
514
|
+
function evaluateFormulaInner(compiled, ctx, session) {
|
|
515
|
+
const inst = compiled.instance;
|
|
516
|
+
const key = session.makeKey(inst.sheetName, inst.row, inst.col);
|
|
517
|
+
// Check cache
|
|
518
|
+
const cached = session.resultCache.get(key);
|
|
519
|
+
if (cached !== undefined) {
|
|
520
|
+
return cached;
|
|
521
|
+
}
|
|
522
|
+
// Circular reference detection. Under iterative calculation the driver
|
|
523
|
+
// (calculate-formulas-impl.ts) seeds `circularFallback` with the previous
|
|
524
|
+
// iteration's result so the re-entrant lookup receives a stable value.
|
|
525
|
+
// Outside of iterative mode the map is empty — we return 0 as the fallback,
|
|
526
|
+
// matching Excel's "iterate with 0 seed" convention. This keeps simple
|
|
527
|
+
// cycles like A1=A1+1 producing a number instead of an error, which is the
|
|
528
|
+
// established behaviour for this engine (tests depend on this). For strict
|
|
529
|
+
// circular-reference error reporting, enable iterative calculation and
|
|
530
|
+
// observe convergence failure, or configure a custom fallback value.
|
|
531
|
+
if (session.evaluating.has(key)) {
|
|
532
|
+
const fallback = session.circularFallback.get(key);
|
|
533
|
+
const val = fallback !== undefined ? fallback : (0, values_1.rvNumber)(0);
|
|
534
|
+
return { scalar: val, raw: val };
|
|
535
|
+
}
|
|
536
|
+
session.evaluating.add(key);
|
|
537
|
+
const prevAddress = ctx.currentAddress;
|
|
538
|
+
const prevSheet = ctx.currentSheet;
|
|
539
|
+
const prevRecording = session.recordingKey;
|
|
540
|
+
ctx.currentAddress = { sheet: inst.sheetName, row: inst.row, col: inst.col };
|
|
541
|
+
ctx.currentSheet = inst.sheetName;
|
|
542
|
+
// Enable runtime dependency recording for formulas with dynamic refs
|
|
543
|
+
if (compiled.hasDynamicRefs) {
|
|
544
|
+
session.recordingKey = key;
|
|
545
|
+
}
|
|
546
|
+
try {
|
|
547
|
+
const result = evaluate(compiled.bound, ctx, session);
|
|
548
|
+
const intersected = implicitIntersect(result, ctx);
|
|
549
|
+
const scalar = dereferenceValue(intersected, ctx, session);
|
|
550
|
+
const raw = dereferenceValue(result, ctx, session);
|
|
551
|
+
const entry = { scalar, raw };
|
|
552
|
+
session.resultCache.set(key, entry);
|
|
553
|
+
// Register the spill region if this is a dynamic-array formula
|
|
554
|
+
// whose result is a multi-cell array. Downstream formulas that
|
|
555
|
+
// read into the spill range (e.g. `=SUM(A1:A5)` over a
|
|
556
|
+
// `=SEQUENCE(5)` master) can now pick up the ghost-cell values
|
|
557
|
+
// before materialize writes them back to the snapshot.
|
|
558
|
+
const isDyn = compiled.instance.isDynamicArray || compiled.isDynamicArrayFunction;
|
|
559
|
+
if (isDyn && raw.kind === 5 /* RVKind.Array */ && (raw.height > 1 || raw.width > 1)) {
|
|
560
|
+
for (let r = 0; r < raw.height; r++) {
|
|
561
|
+
for (let c = 0; c < raw.width; c++) {
|
|
562
|
+
if (r === 0 && c === 0) {
|
|
563
|
+
continue; // master cell already points to its own cache entry
|
|
564
|
+
}
|
|
565
|
+
const targetKey = session.makeKey(inst.sheetName, inst.row + r, inst.col + c);
|
|
566
|
+
session.liveSpills.set(targetKey, {
|
|
567
|
+
masterKey: key,
|
|
568
|
+
rowOffset: r,
|
|
569
|
+
colOffset: c
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
return entry;
|
|
575
|
+
}
|
|
576
|
+
catch (err) {
|
|
577
|
+
// Cache a #CALC! sentinel so a re-entrant lookup for the same cell does
|
|
578
|
+
// not trigger repeated (exponentially growing) re-evaluation under
|
|
579
|
+
// iterative calc or dependent recomputation. The exception is re-thrown
|
|
580
|
+
// so the outer caller can still log / translate it into a sheet error.
|
|
581
|
+
const fallback = { scalar: values_1.ERRORS.CALC, raw: values_1.ERRORS.CALC };
|
|
582
|
+
session.resultCache.set(key, fallback);
|
|
583
|
+
throw err;
|
|
584
|
+
}
|
|
585
|
+
finally {
|
|
586
|
+
session.evaluating.delete(key);
|
|
587
|
+
ctx.currentAddress = prevAddress;
|
|
588
|
+
ctx.currentSheet = prevSheet;
|
|
589
|
+
session.recordingKey = prevRecording;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Evaluate a compiled formula and return its **scalar** result.
|
|
594
|
+
*
|
|
595
|
+
* This is the standard evaluation path for regular (non-array) formulas.
|
|
596
|
+
* The result is:
|
|
597
|
+
* 1. Evaluated from the bound expression tree
|
|
598
|
+
* 2. Implicit-intersected to a single value/reference
|
|
599
|
+
* 3. Dereferenced if it's a reference
|
|
600
|
+
* 4. Cached for subsequent lookups by dependent formulas
|
|
601
|
+
*
|
|
602
|
+
* Use `evaluateFormulaRaw` instead when the full array result is needed
|
|
603
|
+
* (dynamic array formulas, CSE formulas).
|
|
604
|
+
*/
|
|
605
|
+
function evaluateFormula(compiled, ctx, session) {
|
|
606
|
+
return evaluateFormulaInner(compiled, ctx, session).scalar;
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Evaluate a compiled formula and return the **raw** (possibly array) result.
|
|
610
|
+
*
|
|
611
|
+
* This is the evaluation path for dynamic array and CSE formulas where
|
|
612
|
+
* the full array shape must be preserved for spill/distribution.
|
|
613
|
+
*
|
|
614
|
+
* Semantics:
|
|
615
|
+
* - Both scalar and raw forms are stored in `session.resultCache` as a
|
|
616
|
+
* `CachedResult{scalar, raw}`. Dependent scalar formulas see the scalar
|
|
617
|
+
* form; the materialize layer retrieves the raw form.
|
|
618
|
+
* - The return value is the full dereferenced result — may be an ArrayValue
|
|
619
|
+
* with height > 1 or width > 1.
|
|
620
|
+
*/
|
|
621
|
+
function evaluateFormulaRaw(compiled, ctx, session) {
|
|
622
|
+
return evaluateFormulaInner(compiled, ctx, session).raw;
|
|
623
|
+
}
|
|
624
|
+
// ============================================================================
|
|
625
|
+
// Binary Operations
|
|
626
|
+
// ============================================================================
|
|
627
|
+
function evaluateBinaryOp(op, leftExpr, rightExpr, ctx, session) {
|
|
628
|
+
// Intersection operator (whitespace between two refs). Must be handled
|
|
629
|
+
// BEFORE dereferencing so we can inspect the reference areas.
|
|
630
|
+
if (op === " ") {
|
|
631
|
+
return evaluateIntersection(leftExpr, rightExpr, ctx, session);
|
|
632
|
+
}
|
|
633
|
+
// Range operator `:` — union of two references into the bounding
|
|
634
|
+
// rectangle. Needed when one side is a function call (e.g.
|
|
635
|
+
// `B11:INDIRECT("B" & ROW()-1)`). Both sides must be references or
|
|
636
|
+
// coerce to references; otherwise Excel returns #REF!.
|
|
637
|
+
if (op === ":") {
|
|
638
|
+
return evaluateRangeUnion(leftExpr, rightExpr, ctx, session);
|
|
639
|
+
}
|
|
640
|
+
const left = dereferenceValue(evaluate(leftExpr, ctx, session), ctx, session);
|
|
641
|
+
const right = dereferenceValue(evaluate(rightExpr, ctx, session), ctx, session);
|
|
642
|
+
const lIsArr = left.kind === 5 /* RVKind.Array */;
|
|
643
|
+
const rIsArr = right.kind === 5 /* RVKind.Array */;
|
|
644
|
+
if (lIsArr || rIsArr) {
|
|
645
|
+
return broadcastBinaryOp(op, left, right);
|
|
646
|
+
}
|
|
647
|
+
return applyScalarBinaryOp(op, (0, values_1.topLeft)(left), (0, values_1.topLeft)(right));
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Excel's intersection operator — a whitespace character separating two
|
|
651
|
+
* references (e.g. `A1:A10 B1:B10`).
|
|
652
|
+
*
|
|
653
|
+
* Semantics:
|
|
654
|
+
* - Both operands must evaluate to single-area references. Otherwise the
|
|
655
|
+
* result is `#VALUE!` (matches Excel's behaviour when non-refs or
|
|
656
|
+
* multi-area refs are intersected).
|
|
657
|
+
* - Intersection is the rectangle overlap of the two areas on the same
|
|
658
|
+
* sheet.
|
|
659
|
+
* - If the areas do not overlap (or are on different sheets) the result
|
|
660
|
+
* is `#NULL!`, Excel's canonical "empty intersection" error.
|
|
661
|
+
*/
|
|
662
|
+
function evaluateIntersection(leftExpr, rightExpr, ctx, session) {
|
|
663
|
+
const left = evaluate(leftExpr, ctx, session);
|
|
664
|
+
const right = evaluate(rightExpr, ctx, session);
|
|
665
|
+
if ((0, values_1.isError)(left)) {
|
|
666
|
+
return left;
|
|
667
|
+
}
|
|
668
|
+
if ((0, values_1.isError)(right)) {
|
|
669
|
+
return right;
|
|
670
|
+
}
|
|
671
|
+
if (left.kind !== 6 /* RVKind.Reference */ || right.kind !== 6 /* RVKind.Reference */) {
|
|
672
|
+
return values_1.ERRORS.VALUE;
|
|
673
|
+
}
|
|
674
|
+
if (left.areas.length !== 1 || right.areas.length !== 1) {
|
|
675
|
+
return values_1.ERRORS.VALUE;
|
|
676
|
+
}
|
|
677
|
+
const la = left.areas[0];
|
|
678
|
+
const ra = right.areas[0];
|
|
679
|
+
if (la.sheet.toLowerCase() !== ra.sheet.toLowerCase()) {
|
|
680
|
+
return values_1.ERRORS.NULL;
|
|
681
|
+
}
|
|
682
|
+
const top = Math.max(la.top, ra.top);
|
|
683
|
+
const left_ = Math.max(la.left, ra.left);
|
|
684
|
+
const bottom = Math.min(la.bottom, ra.bottom);
|
|
685
|
+
const right_ = Math.min(la.right, ra.right);
|
|
686
|
+
if (top > bottom || left_ > right_) {
|
|
687
|
+
return values_1.ERRORS.NULL;
|
|
688
|
+
}
|
|
689
|
+
return (0, values_1.rvRef)(la.sheet, top, left_, bottom, right_);
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Range operator `:` applied at runtime. Normally `A1:B2` is merged by
|
|
693
|
+
* the tokenizer, but patterns like `A1:INDIRECT("B5")` leave the colon
|
|
694
|
+
* as a standalone operator. Both operands must evaluate to references
|
|
695
|
+
* (single-cell or area); the result is the bounding rectangle of the
|
|
696
|
+
* two reference ranges on the same sheet.
|
|
697
|
+
*
|
|
698
|
+
* Semantics:
|
|
699
|
+
* - Both sides must be references. Literal numbers / strings → #VALUE!.
|
|
700
|
+
* - References must live on the same sheet → else #REF!.
|
|
701
|
+
* - Multi-area references on either side → #REF! (Excel behavior).
|
|
702
|
+
*/
|
|
703
|
+
function evaluateRangeUnion(leftExpr, rightExpr, ctx, session) {
|
|
704
|
+
const left = evaluate(leftExpr, ctx, session);
|
|
705
|
+
const right = evaluate(rightExpr, ctx, session);
|
|
706
|
+
if ((0, values_1.isError)(left)) {
|
|
707
|
+
return left;
|
|
708
|
+
}
|
|
709
|
+
if ((0, values_1.isError)(right)) {
|
|
710
|
+
return right;
|
|
711
|
+
}
|
|
712
|
+
if (left.kind !== 6 /* RVKind.Reference */ || right.kind !== 6 /* RVKind.Reference */) {
|
|
713
|
+
return values_1.ERRORS.VALUE;
|
|
714
|
+
}
|
|
715
|
+
if (left.areas.length !== 1 || right.areas.length !== 1) {
|
|
716
|
+
return values_1.ERRORS.REF;
|
|
717
|
+
}
|
|
718
|
+
const la = left.areas[0];
|
|
719
|
+
const ra = right.areas[0];
|
|
720
|
+
if (la.sheet.toLowerCase() !== ra.sheet.toLowerCase()) {
|
|
721
|
+
return values_1.ERRORS.REF;
|
|
722
|
+
}
|
|
723
|
+
// Bounding rectangle that spans both areas — this is the union /
|
|
724
|
+
// range-op semantics (Excel), distinct from intersection which uses
|
|
725
|
+
// min/max in the opposite direction.
|
|
726
|
+
const top = Math.min(la.top, ra.top);
|
|
727
|
+
const left_ = Math.min(la.left, ra.left);
|
|
728
|
+
const bottom = Math.max(la.bottom, ra.bottom);
|
|
729
|
+
const right_ = Math.max(la.right, ra.right);
|
|
730
|
+
return (0, values_1.rvRef)(la.sheet, top, left_, bottom, right_);
|
|
731
|
+
}
|
|
732
|
+
function applyScalarBinaryOp(op, left, right) {
|
|
733
|
+
if ((0, values_1.isError)(left)) {
|
|
734
|
+
return left;
|
|
735
|
+
}
|
|
736
|
+
if ((0, values_1.isError)(right)) {
|
|
737
|
+
return right;
|
|
738
|
+
}
|
|
739
|
+
// Concatenation
|
|
740
|
+
if (op === "&") {
|
|
741
|
+
const lStr = (0, values_1.toStringRV)(left);
|
|
742
|
+
const rStr = (0, values_1.toStringRV)(right);
|
|
743
|
+
return (0, values_1.rvString)(lStr + rStr);
|
|
744
|
+
}
|
|
745
|
+
// Comparison
|
|
746
|
+
if (op === "=" || op === "<>" || op === "<" || op === ">" || op === "<=" || op === ">=") {
|
|
747
|
+
return (0, values_1.rvBoolean)(compareScalars(left, right, op));
|
|
748
|
+
}
|
|
749
|
+
// Arithmetic
|
|
750
|
+
const lNum = (0, values_1.toNumberRV)(left);
|
|
751
|
+
if ((0, values_1.isError)(lNum)) {
|
|
752
|
+
return lNum;
|
|
753
|
+
}
|
|
754
|
+
const rNum = (0, values_1.toNumberRV)(right);
|
|
755
|
+
if ((0, values_1.isError)(rNum)) {
|
|
756
|
+
return rNum;
|
|
757
|
+
}
|
|
758
|
+
let result;
|
|
759
|
+
switch (op) {
|
|
760
|
+
case "+":
|
|
761
|
+
result = lNum.value + rNum.value;
|
|
762
|
+
break;
|
|
763
|
+
case "-":
|
|
764
|
+
result = lNum.value - rNum.value;
|
|
765
|
+
break;
|
|
766
|
+
case "*":
|
|
767
|
+
result = lNum.value * rNum.value;
|
|
768
|
+
break;
|
|
769
|
+
case "/":
|
|
770
|
+
if (rNum.value === 0) {
|
|
771
|
+
return values_1.ERRORS.DIV0;
|
|
772
|
+
}
|
|
773
|
+
result = lNum.value / rNum.value;
|
|
774
|
+
break;
|
|
775
|
+
case "^":
|
|
776
|
+
// Excel distinguishes `0 ^ n` for n < 0 (→ #DIV/0!, since it's
|
|
777
|
+
// semantically 1/0) from other overflows (→ #NUM!). The generic
|
|
778
|
+
// `isFinite` check below loses that distinction, so route the
|
|
779
|
+
// division-by-zero case explicitly first. 0^0 is conventionally 1
|
|
780
|
+
// (matches Excel and POWER()).
|
|
781
|
+
if (lNum.value === 0) {
|
|
782
|
+
if (rNum.value < 0) {
|
|
783
|
+
return values_1.ERRORS.DIV0;
|
|
784
|
+
}
|
|
785
|
+
if (rNum.value === 0) {
|
|
786
|
+
return (0, values_1.rvNumber)(1);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
result = Math.pow(lNum.value, rNum.value);
|
|
790
|
+
if (Number.isNaN(result)) {
|
|
791
|
+
// `Math.pow(-1, 0.5)` etc. — complex result; Excel reports #NUM!.
|
|
792
|
+
return values_1.ERRORS.NUM;
|
|
793
|
+
}
|
|
794
|
+
break;
|
|
795
|
+
default:
|
|
796
|
+
return values_1.ERRORS.VALUE;
|
|
797
|
+
}
|
|
798
|
+
return !isFinite(result) ? values_1.ERRORS.NUM : (0, values_1.rvNumber)(result);
|
|
799
|
+
}
|
|
800
|
+
function compareScalars(left, right, op) {
|
|
801
|
+
// Normalize blanks to a neutral form of the opposing kind so formulas like
|
|
802
|
+
// `"" = A1` (where A1 is blank) compare equal. Without this normalisation
|
|
803
|
+
// Excel would route us to the cross-type tiebreak below.
|
|
804
|
+
const l = left.kind === 0 /* RVKind.Blank */
|
|
805
|
+
? right.kind === 2 /* RVKind.String */
|
|
806
|
+
? (0, values_1.rvString)("")
|
|
807
|
+
: right.kind === 3 /* RVKind.Boolean */
|
|
808
|
+
? (0, values_1.rvBoolean)(false)
|
|
809
|
+
: (0, values_1.rvNumber)(0)
|
|
810
|
+
: left;
|
|
811
|
+
const r = right.kind === 0 /* RVKind.Blank */
|
|
812
|
+
? left.kind === 2 /* RVKind.String */
|
|
813
|
+
? (0, values_1.rvString)("")
|
|
814
|
+
: left.kind === 3 /* RVKind.Boolean */
|
|
815
|
+
? (0, values_1.rvBoolean)(false)
|
|
816
|
+
: (0, values_1.rvNumber)(0)
|
|
817
|
+
: right;
|
|
818
|
+
let cmp;
|
|
819
|
+
if (l.kind === r.kind) {
|
|
820
|
+
cmp = (0, values_1.compareScalarsSameKind)(l, r);
|
|
821
|
+
if (!Number.isFinite(cmp)) {
|
|
822
|
+
cmp = 0;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
else {
|
|
826
|
+
// Excel orders scalar kinds: Number < String < Boolean < Error/Blank.
|
|
827
|
+
const order = (v) => {
|
|
828
|
+
if (v.kind === 1 /* RVKind.Number */) {
|
|
829
|
+
return 0;
|
|
830
|
+
}
|
|
831
|
+
if (v.kind === 2 /* RVKind.String */) {
|
|
832
|
+
return 1;
|
|
833
|
+
}
|
|
834
|
+
if (v.kind === 3 /* RVKind.Boolean */) {
|
|
835
|
+
return 2;
|
|
836
|
+
}
|
|
837
|
+
return 3;
|
|
838
|
+
};
|
|
839
|
+
cmp = order(l) - order(r);
|
|
840
|
+
}
|
|
841
|
+
switch (op) {
|
|
842
|
+
case "=":
|
|
843
|
+
return cmp === 0;
|
|
844
|
+
case "<>":
|
|
845
|
+
return cmp !== 0;
|
|
846
|
+
case "<":
|
|
847
|
+
return cmp < 0;
|
|
848
|
+
case ">":
|
|
849
|
+
return cmp > 0;
|
|
850
|
+
case "<=":
|
|
851
|
+
return cmp <= 0;
|
|
852
|
+
case ">=":
|
|
853
|
+
return cmp >= 0;
|
|
854
|
+
default:
|
|
855
|
+
return false;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
function broadcastBinaryOp(op, left, right) {
|
|
859
|
+
const lArr = left.kind === 5 /* RVKind.Array */ ? left : null;
|
|
860
|
+
const rArr = right.kind === 5 /* RVKind.Array */ ? right : null;
|
|
861
|
+
const lRows = lArr ? lArr.height : 1;
|
|
862
|
+
const lCols = lArr ? lArr.width : 1;
|
|
863
|
+
const rRows = rArr ? rArr.height : 1;
|
|
864
|
+
const rCols = rArr ? rArr.width : 1;
|
|
865
|
+
const outRows = Math.max(lRows, rRows);
|
|
866
|
+
const outCols = Math.max(lCols, rCols);
|
|
867
|
+
if ((lRows !== 1 && rRows !== 1 && lRows !== rRows) ||
|
|
868
|
+
(lCols !== 1 && rCols !== 1 && lCols !== rCols)) {
|
|
869
|
+
return values_1.ERRORS.VALUE;
|
|
870
|
+
}
|
|
871
|
+
// Guard against pathological broadcasts (e.g. A:A * 1:1 = ~17B cells).
|
|
872
|
+
// 10M cells is well beyond any legitimate array use case.
|
|
873
|
+
if (outRows * outCols > 10000000) {
|
|
874
|
+
return values_1.ERRORS.CALC;
|
|
875
|
+
}
|
|
876
|
+
// Precompute scalar-broadcast values once outside the hot cell loop.
|
|
877
|
+
// When one side is a non-array `RuntimeValue`, it expands to the same
|
|
878
|
+
// `ScalarValue` for every (r, c); repeating `topLeft(left)` inside the
|
|
879
|
+
// inner loop (outRows × outCols calls) was pure overhead.
|
|
880
|
+
const lScalarFallback = lArr ? undefined : (0, values_1.topLeft)(left);
|
|
881
|
+
const rScalarFallback = rArr ? undefined : (0, values_1.topLeft)(right);
|
|
882
|
+
const rows = [];
|
|
883
|
+
for (let r = 0; r < outRows; r++) {
|
|
884
|
+
const row = [];
|
|
885
|
+
for (let c = 0; c < outCols; c++) {
|
|
886
|
+
const lR = lRows === 1 ? 0 : r;
|
|
887
|
+
const lC = lCols === 1 ? 0 : c;
|
|
888
|
+
const rR = rRows === 1 ? 0 : r;
|
|
889
|
+
const rC = rCols === 1 ? 0 : c;
|
|
890
|
+
// Array values are rectangular (normalised by rvArray) so direct
|
|
891
|
+
// indexing is safe; the previous `?? BLANK` fallback was defensive
|
|
892
|
+
// code that never triggered in practice but cost an optional chain
|
|
893
|
+
// per cell in a hot loop.
|
|
894
|
+
const lVal = lArr ? lArr.rows[lR][lC] : lScalarFallback;
|
|
895
|
+
const rVal = rArr ? rArr.rows[rR][rC] : rScalarFallback;
|
|
896
|
+
row.push(applyScalarBinaryOp(op, lVal, rVal));
|
|
897
|
+
}
|
|
898
|
+
rows.push(row);
|
|
899
|
+
}
|
|
900
|
+
// Propagate origin metadata
|
|
901
|
+
const originRow = lArr?.originRow ?? rArr?.originRow;
|
|
902
|
+
const originCol = lArr?.originCol ?? rArr?.originCol;
|
|
903
|
+
return (0, values_1.rvArray)(rows, originRow, originCol);
|
|
904
|
+
}
|
|
905
|
+
// ============================================================================
|
|
906
|
+
// Unary Operations
|
|
907
|
+
// ============================================================================
|
|
908
|
+
function evaluateUnaryOp(op, operandExpr, ctx, session) {
|
|
909
|
+
const rawVal = evaluate(operandExpr, ctx, session);
|
|
910
|
+
// @ implicit intersection
|
|
911
|
+
if (op === "@") {
|
|
912
|
+
const intersected = implicitIntersect(rawVal, ctx);
|
|
913
|
+
return dereferenceValue(intersected, ctx, session);
|
|
914
|
+
}
|
|
915
|
+
const val = dereferenceValue(rawVal, ctx, session);
|
|
916
|
+
if (val.kind === 5 /* RVKind.Array */) {
|
|
917
|
+
const rows = [];
|
|
918
|
+
for (const row of val.rows) {
|
|
919
|
+
rows.push(row.map(cell => applyScalarUnary(op, cell)));
|
|
920
|
+
}
|
|
921
|
+
return (0, values_1.rvArray)(rows, val.originRow, val.originCol);
|
|
922
|
+
}
|
|
923
|
+
return applyScalarUnary(op, (0, values_1.topLeft)(val));
|
|
924
|
+
}
|
|
925
|
+
function applyScalarUnary(op, val) {
|
|
926
|
+
if ((0, values_1.isError)(val)) {
|
|
927
|
+
return val;
|
|
928
|
+
}
|
|
929
|
+
const n = (0, values_1.toNumberRV)(val);
|
|
930
|
+
if ((0, values_1.isError)(n)) {
|
|
931
|
+
return n;
|
|
932
|
+
}
|
|
933
|
+
switch (op) {
|
|
934
|
+
case "-":
|
|
935
|
+
return (0, values_1.rvNumber)(-n.value);
|
|
936
|
+
case "+":
|
|
937
|
+
return n;
|
|
938
|
+
default:
|
|
939
|
+
return values_1.ERRORS.VALUE;
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
// ============================================================================
|
|
943
|
+
// Percent
|
|
944
|
+
// ============================================================================
|
|
945
|
+
function evaluatePercent(operandExpr, ctx, session) {
|
|
946
|
+
const val = dereferenceValue(evaluate(operandExpr, ctx, session), ctx, session);
|
|
947
|
+
if (val.kind === 5 /* RVKind.Array */) {
|
|
948
|
+
const rows = [];
|
|
949
|
+
for (const row of val.rows) {
|
|
950
|
+
rows.push(row.map(cell => {
|
|
951
|
+
if ((0, values_1.isError)(cell)) {
|
|
952
|
+
return cell;
|
|
953
|
+
}
|
|
954
|
+
const n = (0, values_1.toNumberRV)(cell);
|
|
955
|
+
if ((0, values_1.isError)(n)) {
|
|
956
|
+
return n;
|
|
957
|
+
}
|
|
958
|
+
return (0, values_1.rvNumber)(n.value / 100);
|
|
959
|
+
}));
|
|
960
|
+
}
|
|
961
|
+
return (0, values_1.rvArray)(rows);
|
|
962
|
+
}
|
|
963
|
+
const scalar = (0, values_1.topLeft)(val);
|
|
964
|
+
if ((0, values_1.isError)(scalar)) {
|
|
965
|
+
return scalar;
|
|
966
|
+
}
|
|
967
|
+
const n = (0, values_1.toNumberRV)(scalar);
|
|
968
|
+
if ((0, values_1.isError)(n)) {
|
|
969
|
+
return n;
|
|
970
|
+
}
|
|
971
|
+
return (0, values_1.rvNumber)(n.value / 100);
|
|
972
|
+
}
|
|
973
|
+
// ============================================================================
|
|
974
|
+
// Function Call (Eager)
|
|
975
|
+
// ============================================================================
|
|
976
|
+
function evaluateCall(expr, ctx, session) {
|
|
977
|
+
// Reference functions: ROW, COLUMN, ROWS, COLUMNS
|
|
978
|
+
// (Accept _XLFN. prefixed names transparently.)
|
|
979
|
+
const canonical = (0, token_types_1.stripFunctionPrefix)(expr.name);
|
|
980
|
+
const refResult = tryEvaluateRefFunction(canonical, expr.args, ctx);
|
|
981
|
+
if (refResult !== undefined) {
|
|
982
|
+
return refResult;
|
|
983
|
+
}
|
|
984
|
+
// Reference-producing functions like INDIRECT/OFFSET yield a
|
|
985
|
+
// ReferenceValue at runtime. ROW/COLUMN/ROWS/COLUMNS need to inspect
|
|
986
|
+
// the resulting reference's address rather than its dereferenced value,
|
|
987
|
+
// so we evaluate the argument *without* dereferencing and extract the
|
|
988
|
+
// geometry directly. Only the 1-arg reference-only forms go down this
|
|
989
|
+
// path; scalar/array arguments still fall through to the eager fallback
|
|
990
|
+
// below, which returns #VALUE! for ROW/COLUMN and the correct count for
|
|
991
|
+
// ROWS/COLUMNS.
|
|
992
|
+
if (expr.args.length === 1 && isSimpleRefFunction(canonical)) {
|
|
993
|
+
const raw = evaluate(expr.args[0], ctx, session);
|
|
994
|
+
if (raw.kind === 6 /* RVKind.Reference */ && raw.areas.length > 0) {
|
|
995
|
+
const area = raw.areas[0];
|
|
996
|
+
switch (canonical) {
|
|
997
|
+
case "ROW":
|
|
998
|
+
return (0, values_1.rvNumber)(area.top);
|
|
999
|
+
case "COLUMN":
|
|
1000
|
+
return (0, values_1.rvNumber)(area.left);
|
|
1001
|
+
case "ROWS":
|
|
1002
|
+
return (0, values_1.rvNumber)(area.bottom - area.top + 1);
|
|
1003
|
+
case "COLUMNS":
|
|
1004
|
+
return (0, values_1.rvNumber)(area.right - area.left + 1);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
// ── Reference-aware functions (ISREF, CELL) ──
|
|
1009
|
+
// These inspect the argument's reference-ness rather than its dereferenced
|
|
1010
|
+
// value. Handled here so the raw BoundExpr / ReferenceValue is visible.
|
|
1011
|
+
if (canonical === "ISREF") {
|
|
1012
|
+
return evaluateISREF(expr.args, ctx, session);
|
|
1013
|
+
}
|
|
1014
|
+
if (canonical === "CELL") {
|
|
1015
|
+
return evaluateCELL(expr.args, ctx, session);
|
|
1016
|
+
}
|
|
1017
|
+
// Evaluate all arguments eagerly and dereference references
|
|
1018
|
+
const args = expr.args.map(arg => dereferenceValue(evaluate(arg, ctx, session), ctx, session));
|
|
1019
|
+
// Look up function
|
|
1020
|
+
const desc = (0, function_registry_1.lookupFunction)(expr.name);
|
|
1021
|
+
if (desc) {
|
|
1022
|
+
// Validate arity — produce #VALUE! for wrong argument count
|
|
1023
|
+
if (args.length < desc.minArity || args.length > desc.maxArity) {
|
|
1024
|
+
return values_1.ERRORS.VALUE;
|
|
1025
|
+
}
|
|
1026
|
+
// Context-aware overrides for functions that need evaluator state
|
|
1027
|
+
switch (canonical) {
|
|
1028
|
+
case "SHEET": {
|
|
1029
|
+
// SHEET() → current sheet number; SHEET(ref) → sheet number of ref
|
|
1030
|
+
if (args.length === 0) {
|
|
1031
|
+
const idx = ctx.snapshot.worksheets.findIndex(ws => ws.name.toLowerCase() === ctx.currentSheet.toLowerCase());
|
|
1032
|
+
return (0, values_1.rvNumber)(idx >= 0 ? idx + 1 : 1);
|
|
1033
|
+
}
|
|
1034
|
+
return desc.invoke(args);
|
|
1035
|
+
}
|
|
1036
|
+
case "SHEETS": {
|
|
1037
|
+
// SHEETS() → total sheet count
|
|
1038
|
+
if (args.length === 0) {
|
|
1039
|
+
return (0, values_1.rvNumber)(ctx.snapshot.worksheets.length);
|
|
1040
|
+
}
|
|
1041
|
+
return desc.invoke(args);
|
|
1042
|
+
}
|
|
1043
|
+
case "ISFORMULA": {
|
|
1044
|
+
// ISFORMULA requires a reference argument. When the raw argument is
|
|
1045
|
+
// a CellRef/AreaRef we can look up the underlying cell's formulaKind.
|
|
1046
|
+
// Any other shape (literal, computed value, etc.) yields #N/A per
|
|
1047
|
+
// Excel's behavior for non-reference arguments.
|
|
1048
|
+
const raw = expr.args[0];
|
|
1049
|
+
if (raw && raw.kind === 2 /* BoundExprKind.CellRef */) {
|
|
1050
|
+
const ws = ctx.snapshot.worksheetsByName.get(raw.sheet.toLowerCase());
|
|
1051
|
+
if (!ws) {
|
|
1052
|
+
return values_1.ERRORS.REF;
|
|
1053
|
+
}
|
|
1054
|
+
const cell = ws.cells.get((0, workbook_snapshot_1.snapshotCellKey)(raw.row, raw.col));
|
|
1055
|
+
return (0, values_1.rvBoolean)(cell !== undefined && cell.formulaKind !== "none");
|
|
1056
|
+
}
|
|
1057
|
+
if (raw && raw.kind === 3 /* BoundExprKind.AreaRef */) {
|
|
1058
|
+
const ws = ctx.snapshot.worksheetsByName.get(raw.sheet.toLowerCase());
|
|
1059
|
+
if (!ws) {
|
|
1060
|
+
return values_1.ERRORS.REF;
|
|
1061
|
+
}
|
|
1062
|
+
// ISFORMULA on an area ref inspects the top-left cell.
|
|
1063
|
+
const cell = ws.cells.get((0, workbook_snapshot_1.snapshotCellKey)(raw.top, raw.left));
|
|
1064
|
+
return (0, values_1.rvBoolean)(cell !== undefined && cell.formulaKind !== "none");
|
|
1065
|
+
}
|
|
1066
|
+
return values_1.ERRORS.NA;
|
|
1067
|
+
}
|
|
1068
|
+
case "FORMULATEXT": {
|
|
1069
|
+
// FORMULATEXT returns the formula source text at the referenced cell,
|
|
1070
|
+
// or #N/A if the cell has no formula. Non-reference arguments also
|
|
1071
|
+
// yield #N/A.
|
|
1072
|
+
const raw = expr.args[0];
|
|
1073
|
+
if (raw && raw.kind === 2 /* BoundExprKind.CellRef */) {
|
|
1074
|
+
const ws = ctx.snapshot.worksheetsByName.get(raw.sheet.toLowerCase());
|
|
1075
|
+
if (!ws) {
|
|
1076
|
+
return values_1.ERRORS.REF;
|
|
1077
|
+
}
|
|
1078
|
+
const cell = ws.cells.get((0, workbook_snapshot_1.snapshotCellKey)(raw.row, raw.col));
|
|
1079
|
+
if (cell && cell.formulaKind !== "none" && cell.formula !== undefined) {
|
|
1080
|
+
return (0, values_1.rvString)(`=${cell.formula}`);
|
|
1081
|
+
}
|
|
1082
|
+
return values_1.ERRORS.NA;
|
|
1083
|
+
}
|
|
1084
|
+
if (raw && raw.kind === 3 /* BoundExprKind.AreaRef */) {
|
|
1085
|
+
const ws = ctx.snapshot.worksheetsByName.get(raw.sheet.toLowerCase());
|
|
1086
|
+
if (!ws) {
|
|
1087
|
+
return values_1.ERRORS.REF;
|
|
1088
|
+
}
|
|
1089
|
+
const cell = ws.cells.get((0, workbook_snapshot_1.snapshotCellKey)(raw.top, raw.left));
|
|
1090
|
+
if (cell && cell.formulaKind !== "none" && cell.formula !== undefined) {
|
|
1091
|
+
return (0, values_1.rvString)(`=${cell.formula}`);
|
|
1092
|
+
}
|
|
1093
|
+
return values_1.ERRORS.NA;
|
|
1094
|
+
}
|
|
1095
|
+
return values_1.ERRORS.NA;
|
|
1096
|
+
}
|
|
1097
|
+
default:
|
|
1098
|
+
return desc.invoke(args);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
// Check if name resolves to a lambda (defined name or local binding)
|
|
1102
|
+
const lambda = resolveLambdaName(expr.name, args, ctx, session);
|
|
1103
|
+
if (lambda !== undefined) {
|
|
1104
|
+
return lambda;
|
|
1105
|
+
}
|
|
1106
|
+
return values_1.ERRORS.NAME;
|
|
1107
|
+
}
|
|
1108
|
+
// ============================================================================
|
|
1109
|
+
// Special Form Call (Lazy)
|
|
1110
|
+
// ============================================================================
|
|
1111
|
+
function evaluateSpecialCall(expr, ctx, session) {
|
|
1112
|
+
switch (expr.name) {
|
|
1113
|
+
case "IF":
|
|
1114
|
+
return evaluateIF(expr.args, ctx, session);
|
|
1115
|
+
case "IFERROR":
|
|
1116
|
+
return evaluateIFERROR(expr.args, ctx, session);
|
|
1117
|
+
case "IFNA":
|
|
1118
|
+
return evaluateIFNA(expr.args, ctx, session);
|
|
1119
|
+
case "IFS":
|
|
1120
|
+
return evaluateIFS(expr.args, ctx, session);
|
|
1121
|
+
case "SWITCH":
|
|
1122
|
+
return evaluateSWITCH(expr.args, ctx, session);
|
|
1123
|
+
case "CHOOSE":
|
|
1124
|
+
return evaluateCHOOSE(expr.args, ctx, session);
|
|
1125
|
+
case "LET":
|
|
1126
|
+
return evaluateLET(expr.args, ctx, session);
|
|
1127
|
+
case "LAMBDA":
|
|
1128
|
+
return evaluateLAMBDA(expr.args, ctx);
|
|
1129
|
+
case "INDIRECT":
|
|
1130
|
+
return evaluateINDIRECT(expr.args, ctx, session);
|
|
1131
|
+
case "OFFSET":
|
|
1132
|
+
return evaluateOFFSET(expr.args, ctx, session);
|
|
1133
|
+
case "MAP":
|
|
1134
|
+
case "REDUCE":
|
|
1135
|
+
case "SCAN":
|
|
1136
|
+
case "MAKEARRAY":
|
|
1137
|
+
case "BYROW":
|
|
1138
|
+
case "BYCOL":
|
|
1139
|
+
return evaluateHigherOrder(expr.name, expr.args, ctx, session);
|
|
1140
|
+
default:
|
|
1141
|
+
return values_1.ERRORS.VALUE;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
// ============================================================================
|
|
1145
|
+
// Special Forms Implementation
|
|
1146
|
+
// ============================================================================
|
|
1147
|
+
function evaluateIF(args, ctx, session) {
|
|
1148
|
+
if (args.length < 2) {
|
|
1149
|
+
return values_1.ERRORS.VALUE;
|
|
1150
|
+
}
|
|
1151
|
+
const condRaw = evalDeref(args[0], ctx, session);
|
|
1152
|
+
// Array condition: element-wise IF. Excel's dynamic-array mode makes
|
|
1153
|
+
// `IF({TRUE,FALSE,TRUE}, "Y", "N")` return `{"Y","N","Y"}`. The branches
|
|
1154
|
+
// are evaluated eagerly — Excel does this too because array broadcasting
|
|
1155
|
+
// requires both shapes to be known — and each cell in the output picks
|
|
1156
|
+
// from the corresponding cell of the chosen branch (with scalar branches
|
|
1157
|
+
// broadcasting to fill the condition array's shape).
|
|
1158
|
+
if (condRaw.kind === 5 /* RVKind.Array */) {
|
|
1159
|
+
const trueVal = evalDeref(args[1], ctx, session);
|
|
1160
|
+
const falseVal = args.length > 2 ? evalDeref(args[2], ctx, session) : (0, values_1.rvBoolean)(false);
|
|
1161
|
+
const rows = [];
|
|
1162
|
+
for (let r = 0; r < condRaw.height; r++) {
|
|
1163
|
+
const outRow = [];
|
|
1164
|
+
for (let c = 0; c < condRaw.width; c++) {
|
|
1165
|
+
const cell = condRaw.rows[r][c];
|
|
1166
|
+
if (cell.kind === 4 /* RVKind.Error */) {
|
|
1167
|
+
outRow.push(cell);
|
|
1168
|
+
continue;
|
|
1169
|
+
}
|
|
1170
|
+
const b = (0, values_1.toBooleanRV)(cell);
|
|
1171
|
+
if (b.kind === 4 /* RVKind.Error */) {
|
|
1172
|
+
outRow.push(b);
|
|
1173
|
+
continue;
|
|
1174
|
+
}
|
|
1175
|
+
const branch = b.value ? trueVal : falseVal;
|
|
1176
|
+
outRow.push(pickCellBroadcast(branch, r, c));
|
|
1177
|
+
}
|
|
1178
|
+
rows.push(outRow);
|
|
1179
|
+
}
|
|
1180
|
+
return (0, values_1.rvArray)(rows);
|
|
1181
|
+
}
|
|
1182
|
+
const cond = (0, values_1.topLeft)(condRaw);
|
|
1183
|
+
if ((0, values_1.isError)(cond)) {
|
|
1184
|
+
return cond;
|
|
1185
|
+
}
|
|
1186
|
+
const bool = (0, values_1.toBooleanRV)(cond);
|
|
1187
|
+
if ((0, values_1.isError)(bool)) {
|
|
1188
|
+
return bool;
|
|
1189
|
+
}
|
|
1190
|
+
if (bool.value) {
|
|
1191
|
+
return evaluate(args[1], ctx, session);
|
|
1192
|
+
}
|
|
1193
|
+
return args.length > 2 ? evaluate(args[2], ctx, session) : (0, values_1.rvBoolean)(false);
|
|
1194
|
+
}
|
|
1195
|
+
/**
|
|
1196
|
+
* Pick a scalar from `branch` corresponding to grid position (r, c), with
|
|
1197
|
+
* broadcasting: scalar branches are repeated; smaller arrays are indexed
|
|
1198
|
+
* modulo their bounds (out-of-range → BLANK) matching Excel's array
|
|
1199
|
+
* alignment rules for IF/IFS/etc.
|
|
1200
|
+
*/
|
|
1201
|
+
function pickCellBroadcast(branch, r, c) {
|
|
1202
|
+
if (branch.kind !== 5 /* RVKind.Array */) {
|
|
1203
|
+
return (0, values_1.topLeft)(branch);
|
|
1204
|
+
}
|
|
1205
|
+
const row = r < branch.height ? r : branch.height === 1 ? 0 : -1;
|
|
1206
|
+
const col = c < branch.width ? c : branch.width === 1 ? 0 : -1;
|
|
1207
|
+
if (row < 0 || col < 0) {
|
|
1208
|
+
// Misaligned array branch (smaller than the condition, and not a
|
|
1209
|
+
// broadcastable 1-row / 1-column shape). Excel fills the gaps with
|
|
1210
|
+
// `#N/A` rather than BLANK, so downstream consumers can distinguish
|
|
1211
|
+
// "branch didn't cover this cell" from "branch actually returned
|
|
1212
|
+
// empty". (R6-P1-7)
|
|
1213
|
+
return values_1.ERRORS.NA;
|
|
1214
|
+
}
|
|
1215
|
+
return branch.rows[row][col];
|
|
1216
|
+
}
|
|
1217
|
+
/**
|
|
1218
|
+
* Shared array-aware error replacement used by IFERROR and IFNA. Scans `val`
|
|
1219
|
+
* for cells matching `isMatch`; if any are found, replaces each with the
|
|
1220
|
+
* top-left scalar of `replacement` (lazily evaluated). Scalar inputs follow
|
|
1221
|
+
* the same match-or-pass-through logic.
|
|
1222
|
+
*/
|
|
1223
|
+
function replaceErrorsIn(val, args, ctx, session, isMatch) {
|
|
1224
|
+
if (val.kind === 5 /* RVKind.Array */) {
|
|
1225
|
+
let anyMatch = false;
|
|
1226
|
+
for (const row of val.rows) {
|
|
1227
|
+
for (const cell of row) {
|
|
1228
|
+
if (cell.kind === 4 /* RVKind.Error */ && isMatch(cell)) {
|
|
1229
|
+
anyMatch = true;
|
|
1230
|
+
break;
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
if (anyMatch) {
|
|
1234
|
+
break;
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
if (!anyMatch) {
|
|
1238
|
+
return val;
|
|
1239
|
+
}
|
|
1240
|
+
const replaceScalar = (0, values_1.topLeft)(evalDeref(args[1], ctx, session));
|
|
1241
|
+
const rows = [];
|
|
1242
|
+
for (const row of val.rows) {
|
|
1243
|
+
const newRow = [];
|
|
1244
|
+
for (const cell of row) {
|
|
1245
|
+
newRow.push(cell.kind === 4 /* RVKind.Error */ && isMatch(cell) ? replaceScalar : cell);
|
|
1246
|
+
}
|
|
1247
|
+
rows.push(newRow);
|
|
1248
|
+
}
|
|
1249
|
+
return (0, values_1.rvArray)(rows);
|
|
1250
|
+
}
|
|
1251
|
+
return (0, values_1.isError)(val) && isMatch(val) ? evalDeref(args[1], ctx, session) : val;
|
|
1252
|
+
}
|
|
1253
|
+
function evaluateIFERROR(args, ctx, session) {
|
|
1254
|
+
if (args.length < 2) {
|
|
1255
|
+
return values_1.ERRORS.VALUE;
|
|
1256
|
+
}
|
|
1257
|
+
const val = evalDeref(args[0], ctx, session);
|
|
1258
|
+
return replaceErrorsIn(val, args, ctx, session, () => true);
|
|
1259
|
+
}
|
|
1260
|
+
function evaluateIFNA(args, ctx, session) {
|
|
1261
|
+
if (args.length < 2) {
|
|
1262
|
+
return values_1.ERRORS.VALUE;
|
|
1263
|
+
}
|
|
1264
|
+
const val = evalDeref(args[0], ctx, session);
|
|
1265
|
+
return replaceErrorsIn(val, args, ctx, session, err => err.code === "#N/A");
|
|
1266
|
+
}
|
|
1267
|
+
function evaluateIFS(args, ctx, session) {
|
|
1268
|
+
if (args.length < 2) {
|
|
1269
|
+
return values_1.ERRORS.VALUE;
|
|
1270
|
+
}
|
|
1271
|
+
// Excel requires IFS args to come in test/value pairs. Odd-length
|
|
1272
|
+
// arg lists imply a trailing test with no value and are #N/A.
|
|
1273
|
+
if (args.length % 2 !== 0) {
|
|
1274
|
+
return values_1.ERRORS.NA;
|
|
1275
|
+
}
|
|
1276
|
+
for (let i = 0; i < args.length - 1; i += 2) {
|
|
1277
|
+
const cond = (0, values_1.topLeft)(evalDeref(args[i], ctx, session));
|
|
1278
|
+
if ((0, values_1.isError)(cond)) {
|
|
1279
|
+
return cond;
|
|
1280
|
+
}
|
|
1281
|
+
const bool = (0, values_1.toBooleanRV)(cond);
|
|
1282
|
+
if ((0, values_1.isError)(bool)) {
|
|
1283
|
+
return bool;
|
|
1284
|
+
}
|
|
1285
|
+
if (bool.value) {
|
|
1286
|
+
return evaluate(args[i + 1], ctx, session);
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
return values_1.ERRORS.NA;
|
|
1290
|
+
}
|
|
1291
|
+
function evaluateSWITCH(args, ctx, session) {
|
|
1292
|
+
if (args.length < 3) {
|
|
1293
|
+
return values_1.ERRORS.VALUE;
|
|
1294
|
+
}
|
|
1295
|
+
const expr = (0, values_1.topLeft)(evalDeref(args[0], ctx, session));
|
|
1296
|
+
if ((0, values_1.isError)(expr)) {
|
|
1297
|
+
return expr;
|
|
1298
|
+
}
|
|
1299
|
+
for (let i = 1; i < args.length - 1; i += 2) {
|
|
1300
|
+
const caseVal = (0, values_1.topLeft)(evalDeref(args[i], ctx, session));
|
|
1301
|
+
if ((0, values_1.scalarEquals)(expr, caseVal)) {
|
|
1302
|
+
return evaluate(args[i + 1], ctx, session);
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
if (args.length % 2 === 0) {
|
|
1306
|
+
return evaluate(args[args.length - 1], ctx, session);
|
|
1307
|
+
}
|
|
1308
|
+
return values_1.ERRORS.NA;
|
|
1309
|
+
}
|
|
1310
|
+
function evaluateCHOOSE(args, ctx, session) {
|
|
1311
|
+
if (args.length < 2) {
|
|
1312
|
+
return values_1.ERRORS.VALUE;
|
|
1313
|
+
}
|
|
1314
|
+
const idxVal = (0, values_1.topLeft)(evalDeref(args[0], ctx, session));
|
|
1315
|
+
if ((0, values_1.isError)(idxVal)) {
|
|
1316
|
+
return idxVal;
|
|
1317
|
+
}
|
|
1318
|
+
const num = (0, values_1.toNumberRV)(idxVal);
|
|
1319
|
+
if ((0, values_1.isError)(num)) {
|
|
1320
|
+
return num;
|
|
1321
|
+
}
|
|
1322
|
+
const idx = Math.floor(num.value);
|
|
1323
|
+
if (idx < 1 || idx >= args.length) {
|
|
1324
|
+
return values_1.ERRORS.VALUE;
|
|
1325
|
+
}
|
|
1326
|
+
return evaluate(args[idx], ctx, session);
|
|
1327
|
+
}
|
|
1328
|
+
function evaluateLET(args, ctx, session) {
|
|
1329
|
+
if (args.length < 3 || args.length % 2 !== 1) {
|
|
1330
|
+
return values_1.ERRORS.VALUE;
|
|
1331
|
+
}
|
|
1332
|
+
const prevBindings = ctx.localBindings;
|
|
1333
|
+
const newBindings = new Map(prevBindings);
|
|
1334
|
+
try {
|
|
1335
|
+
const pairCount = (args.length - 1) / 2;
|
|
1336
|
+
for (let i = 0; i < pairCount; i++) {
|
|
1337
|
+
const nameExpr = args[i * 2];
|
|
1338
|
+
const valueExpr = args[i * 2 + 1];
|
|
1339
|
+
if (nameExpr.kind !== 13 /* BoundExprKind.NameExpr */) {
|
|
1340
|
+
return values_1.ERRORS.VALUE;
|
|
1341
|
+
}
|
|
1342
|
+
ctx.localBindings = newBindings;
|
|
1343
|
+
const val = evaluate(valueExpr, ctx, session);
|
|
1344
|
+
newBindings.set(nameExpr.upperName, val);
|
|
1345
|
+
}
|
|
1346
|
+
ctx.localBindings = newBindings;
|
|
1347
|
+
return evaluate(args[args.length - 1], ctx, session);
|
|
1348
|
+
}
|
|
1349
|
+
finally {
|
|
1350
|
+
ctx.localBindings = prevBindings;
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
function evaluateLAMBDA(args, ctx) {
|
|
1354
|
+
if (args.length < 1) {
|
|
1355
|
+
return values_1.ERRORS.VALUE;
|
|
1356
|
+
}
|
|
1357
|
+
const paramExprs = args.slice(0, -1);
|
|
1358
|
+
const bodyExpr = args[args.length - 1];
|
|
1359
|
+
const params = [];
|
|
1360
|
+
for (const p of paramExprs) {
|
|
1361
|
+
if (p.kind !== 13 /* BoundExprKind.NameExpr */) {
|
|
1362
|
+
return values_1.ERRORS.VALUE;
|
|
1363
|
+
}
|
|
1364
|
+
params.push(p.upperName);
|
|
1365
|
+
}
|
|
1366
|
+
return (0, values_1.rvLambda)(params, bodyExpr, ctx.localBindings ? new Map(ctx.localBindings) : undefined);
|
|
1367
|
+
}
|
|
1368
|
+
function evaluateINDIRECT(args, ctx, session) {
|
|
1369
|
+
if (args.length < 1) {
|
|
1370
|
+
return values_1.ERRORS.VALUE;
|
|
1371
|
+
}
|
|
1372
|
+
const refArg = evalDeref(args[0], ctx, session);
|
|
1373
|
+
const refText = (0, values_1.toStringRV)((0, values_1.topLeft)(refArg));
|
|
1374
|
+
if (!refText) {
|
|
1375
|
+
return values_1.ERRORS.REF;
|
|
1376
|
+
}
|
|
1377
|
+
let a1 = true;
|
|
1378
|
+
if (args.length >= 2) {
|
|
1379
|
+
const a1Val = (0, values_1.topLeft)(evalDeref(args[1], ctx, session));
|
|
1380
|
+
a1 =
|
|
1381
|
+
!(a1Val.kind === 3 /* RVKind.Boolean */ && !a1Val.value) &&
|
|
1382
|
+
!(a1Val.kind === 1 /* RVKind.Number */ && a1Val.value === 0);
|
|
1383
|
+
}
|
|
1384
|
+
if (!a1) {
|
|
1385
|
+
// R1C1 — delegate to runtime R1C1 parser
|
|
1386
|
+
return resolveR1C1(refText, ctx, session);
|
|
1387
|
+
}
|
|
1388
|
+
// A1 style — parse and bind at runtime
|
|
1389
|
+
try {
|
|
1390
|
+
// Use NUL (U+0000) as the separator so sheet names containing `__`
|
|
1391
|
+
// can't collide with distinct INDIRECT call sites. Neither formula
|
|
1392
|
+
// text nor an Excel sheet name is allowed to contain `\0`, so the
|
|
1393
|
+
// key is unambiguous. (R6-P1-12)
|
|
1394
|
+
const cacheKey = `${ctx.currentSheet}\u0000${refText}`;
|
|
1395
|
+
let bound = session.indirectAstCache.get(cacheKey);
|
|
1396
|
+
if (bound) {
|
|
1397
|
+
// LRU touch: delete-then-set moves the hit entry to the Map's
|
|
1398
|
+
// insertion-order tail so the oldest entry is always `keys().next()`.
|
|
1399
|
+
session.indirectAstCache.delete(cacheKey);
|
|
1400
|
+
session.indirectAstCache.set(cacheKey, bound);
|
|
1401
|
+
}
|
|
1402
|
+
else {
|
|
1403
|
+
const tokens = (0, tokenizer_1.tokenize)(refText);
|
|
1404
|
+
const ast = (0, parser_1.parse)(tokens);
|
|
1405
|
+
const bindCtx = { snapshot: ctx.snapshot, currentSheet: ctx.currentSheet };
|
|
1406
|
+
bound = (0, binder_1.bind)(ast, bindCtx);
|
|
1407
|
+
// Bound the cache so an adversarial formula that generates a fresh
|
|
1408
|
+
// INDIRECT string every call can't grow session memory unbounded.
|
|
1409
|
+
// The cap matches `astCache` in calculate-formulas-impl.ts. (R6
|
|
1410
|
+
// architectural note #6)
|
|
1411
|
+
if (session.indirectAstCache.size >= 10000) {
|
|
1412
|
+
const oldestKey = session.indirectAstCache.keys().next().value;
|
|
1413
|
+
if (oldestKey !== undefined) {
|
|
1414
|
+
session.indirectAstCache.delete(oldestKey);
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
session.indirectAstCache.set(cacheKey, bound);
|
|
1418
|
+
}
|
|
1419
|
+
return evaluate(bound, ctx, session);
|
|
1420
|
+
}
|
|
1421
|
+
catch {
|
|
1422
|
+
return values_1.ERRORS.REF;
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
function evaluateOFFSET(args, ctx, session) {
|
|
1426
|
+
if (args.length < 3) {
|
|
1427
|
+
return values_1.ERRORS.VALUE;
|
|
1428
|
+
}
|
|
1429
|
+
const refExpr = args[0];
|
|
1430
|
+
let baseRow;
|
|
1431
|
+
let baseCol;
|
|
1432
|
+
let baseSheet;
|
|
1433
|
+
// Remember the base reference's shape — Excel's OFFSET uses it as the
|
|
1434
|
+
// default height/width when those optional arguments are omitted.
|
|
1435
|
+
let baseHeight;
|
|
1436
|
+
let baseWidth;
|
|
1437
|
+
if (refExpr.kind === 2 /* BoundExprKind.CellRef */) {
|
|
1438
|
+
baseRow = refExpr.row;
|
|
1439
|
+
baseCol = refExpr.col;
|
|
1440
|
+
baseSheet = refExpr.sheet;
|
|
1441
|
+
baseHeight = 1;
|
|
1442
|
+
baseWidth = 1;
|
|
1443
|
+
}
|
|
1444
|
+
else if (refExpr.kind === 3 /* BoundExprKind.AreaRef */) {
|
|
1445
|
+
baseRow = refExpr.top;
|
|
1446
|
+
baseCol = refExpr.left;
|
|
1447
|
+
baseSheet = refExpr.sheet;
|
|
1448
|
+
baseHeight = refExpr.bottom - refExpr.top + 1;
|
|
1449
|
+
baseWidth = refExpr.right - refExpr.left + 1;
|
|
1450
|
+
}
|
|
1451
|
+
else {
|
|
1452
|
+
return values_1.ERRORS.VALUE;
|
|
1453
|
+
}
|
|
1454
|
+
const rowsVal = (0, values_1.topLeft)(evalDeref(args[1], ctx, session));
|
|
1455
|
+
const rowsNum = (0, values_1.toNumberRV)(rowsVal);
|
|
1456
|
+
if ((0, values_1.isError)(rowsNum)) {
|
|
1457
|
+
return rowsNum;
|
|
1458
|
+
}
|
|
1459
|
+
const colsVal = (0, values_1.topLeft)(evalDeref(args[2], ctx, session));
|
|
1460
|
+
const colsNum = (0, values_1.toNumberRV)(colsVal);
|
|
1461
|
+
if ((0, values_1.isError)(colsNum)) {
|
|
1462
|
+
return colsNum;
|
|
1463
|
+
}
|
|
1464
|
+
// Excel truncates fractional rows/cols toward zero (not floor). Without
|
|
1465
|
+
// the `Math.trunc`, `OFFSET(A5, -0.7, 0)` would resolve to row 4.3 and
|
|
1466
|
+
// then fail the Map lookup silently, returning BLANK instead of A5.
|
|
1467
|
+
const newRow = baseRow + Math.trunc(rowsNum.value);
|
|
1468
|
+
const newCol = baseCol + Math.trunc(colsNum.value);
|
|
1469
|
+
if (newRow < 1 || newCol < 1 || newRow > 1048576 || newCol > 16384) {
|
|
1470
|
+
return values_1.ERRORS.REF;
|
|
1471
|
+
}
|
|
1472
|
+
// Default height / width come from the base reference itself. OFFSET
|
|
1473
|
+
// only shrinks/expands when the caller passes an explicit non-missing
|
|
1474
|
+
// fourth/fifth argument. A `MissingNode` (compile-time "omitted
|
|
1475
|
+
// argument") binds to a `null`-valued literal; we treat that the same
|
|
1476
|
+
// as "no argument provided" so `OFFSET(A1:C3, 0, 0, , )` keeps the
|
|
1477
|
+
// 3-row × 3-col span instead of collapsing to #REF! with `height = 0`.
|
|
1478
|
+
const isOmitted = (a) => a.kind === 1 /* BoundExprKind.Literal */ && a.value === null && a.errorCode === undefined;
|
|
1479
|
+
let height = baseHeight;
|
|
1480
|
+
let width = baseWidth;
|
|
1481
|
+
if (args.length > 3 && !isOmitted(args[3])) {
|
|
1482
|
+
const h = (0, values_1.toNumberRV)((0, values_1.topLeft)(evalDeref(args[3], ctx, session)));
|
|
1483
|
+
if ((0, values_1.isError)(h)) {
|
|
1484
|
+
return h;
|
|
1485
|
+
}
|
|
1486
|
+
height = Math.trunc(h.value);
|
|
1487
|
+
if (height === 0) {
|
|
1488
|
+
return values_1.ERRORS.REF;
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
if (args.length > 4 && !isOmitted(args[4])) {
|
|
1492
|
+
const w = (0, values_1.toNumberRV)((0, values_1.topLeft)(evalDeref(args[4], ctx, session)));
|
|
1493
|
+
if ((0, values_1.isError)(w)) {
|
|
1494
|
+
return w;
|
|
1495
|
+
}
|
|
1496
|
+
width = Math.trunc(w.value);
|
|
1497
|
+
if (width === 0) {
|
|
1498
|
+
return values_1.ERRORS.REF;
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
// Resolve range coordinates — negative height/width extend upward/leftward
|
|
1502
|
+
let top = newRow;
|
|
1503
|
+
let bottom = newRow + height - 1;
|
|
1504
|
+
if (height < 0) {
|
|
1505
|
+
top = newRow + height + 1;
|
|
1506
|
+
bottom = newRow;
|
|
1507
|
+
}
|
|
1508
|
+
let left = newCol;
|
|
1509
|
+
let right = newCol + width - 1;
|
|
1510
|
+
if (width < 0) {
|
|
1511
|
+
left = newCol + width + 1;
|
|
1512
|
+
right = newCol;
|
|
1513
|
+
}
|
|
1514
|
+
if (top < 1 || left < 1 || bottom > 1048576 || right > 16384) {
|
|
1515
|
+
return values_1.ERRORS.REF;
|
|
1516
|
+
}
|
|
1517
|
+
if (top === bottom && left === right) {
|
|
1518
|
+
return getCellValue(baseSheet, top, left, ctx, session);
|
|
1519
|
+
}
|
|
1520
|
+
return buildRangeArray(ctx, session, baseSheet, top, left, bottom, right);
|
|
1521
|
+
}
|
|
1522
|
+
// ============================================================================
|
|
1523
|
+
// Higher-Order Functions
|
|
1524
|
+
// ============================================================================
|
|
1525
|
+
function evaluateHigherOrder(name, args, ctx, session) {
|
|
1526
|
+
switch (name) {
|
|
1527
|
+
case "MAP":
|
|
1528
|
+
return evaluateMAP(args, ctx, session);
|
|
1529
|
+
case "REDUCE":
|
|
1530
|
+
return evaluateREDUCE(args, ctx, session);
|
|
1531
|
+
case "SCAN":
|
|
1532
|
+
return evaluateSCAN(args, ctx, session);
|
|
1533
|
+
case "MAKEARRAY":
|
|
1534
|
+
return evaluateMAKEARRAY(args, ctx, session);
|
|
1535
|
+
case "BYROW":
|
|
1536
|
+
return evaluateBYROW(args, ctx, session);
|
|
1537
|
+
case "BYCOL":
|
|
1538
|
+
return evaluateBYCOL(args, ctx, session);
|
|
1539
|
+
default:
|
|
1540
|
+
return values_1.ERRORS.VALUE;
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
function invokeLambda(lambda, lambdaArgs, ctx, session) {
|
|
1544
|
+
if (lambdaArgs.length !== lambda.params.length) {
|
|
1545
|
+
return values_1.ERRORS.VALUE;
|
|
1546
|
+
}
|
|
1547
|
+
if (session.lambdaDepth >= 256) {
|
|
1548
|
+
return values_1.ERRORS.NUM;
|
|
1549
|
+
}
|
|
1550
|
+
session.lambdaDepth++;
|
|
1551
|
+
try {
|
|
1552
|
+
const prevBindings = ctx.localBindings;
|
|
1553
|
+
const newBindings = new Map(lambda.closureBindings);
|
|
1554
|
+
for (let i = 0; i < lambda.params.length; i++) {
|
|
1555
|
+
newBindings.set(lambda.params[i], lambdaArgs[i]);
|
|
1556
|
+
}
|
|
1557
|
+
ctx.localBindings = newBindings;
|
|
1558
|
+
try {
|
|
1559
|
+
return dereferenceValue(evaluate(lambda.body, ctx, session), ctx, session);
|
|
1560
|
+
}
|
|
1561
|
+
finally {
|
|
1562
|
+
ctx.localBindings = prevBindings;
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
finally {
|
|
1566
|
+
session.lambdaDepth--;
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
function evaluateMAP(args, ctx, session) {
|
|
1570
|
+
if (args.length < 2) {
|
|
1571
|
+
return values_1.ERRORS.VALUE;
|
|
1572
|
+
}
|
|
1573
|
+
const arrVal = evalDeref(args[0], ctx, session);
|
|
1574
|
+
const lambdaVal = evalDeref(args[args.length - 1], ctx, session);
|
|
1575
|
+
if (!(0, values_1.isLambda)(lambdaVal)) {
|
|
1576
|
+
return values_1.ERRORS.VALUE;
|
|
1577
|
+
}
|
|
1578
|
+
if (arrVal.kind !== 5 /* RVKind.Array */) {
|
|
1579
|
+
return invokeLambda(lambdaVal, [arrVal], ctx, session);
|
|
1580
|
+
}
|
|
1581
|
+
const rows = [];
|
|
1582
|
+
for (const row of arrVal.rows) {
|
|
1583
|
+
const outRow = [];
|
|
1584
|
+
for (const cell of row) {
|
|
1585
|
+
outRow.push((0, values_1.topLeft)(invokeLambda(lambdaVal, [cell], ctx, session)));
|
|
1586
|
+
}
|
|
1587
|
+
rows.push(outRow);
|
|
1588
|
+
}
|
|
1589
|
+
return (0, values_1.rvArray)(rows);
|
|
1590
|
+
}
|
|
1591
|
+
function evaluateREDUCE(args, ctx, session) {
|
|
1592
|
+
if (args.length < 3) {
|
|
1593
|
+
return values_1.ERRORS.VALUE;
|
|
1594
|
+
}
|
|
1595
|
+
let acc = evalDeref(args[0], ctx, session);
|
|
1596
|
+
const arrVal = evalDeref(args[1], ctx, session);
|
|
1597
|
+
const lambdaVal = evalDeref(args[2], ctx, session);
|
|
1598
|
+
if (!(0, values_1.isLambda)(lambdaVal)) {
|
|
1599
|
+
return values_1.ERRORS.VALUE;
|
|
1600
|
+
}
|
|
1601
|
+
if (arrVal.kind === 5 /* RVKind.Array */) {
|
|
1602
|
+
for (const row of arrVal.rows) {
|
|
1603
|
+
for (const cell of row) {
|
|
1604
|
+
acc = invokeLambda(lambdaVal, [(0, values_1.topLeft)(acc), cell], ctx, session);
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
else {
|
|
1609
|
+
// Scalar input: Excel treats the scalar as a 1×1 array and invokes the
|
|
1610
|
+
// reducer exactly once. Previously we returned `init` unchanged, which
|
|
1611
|
+
// silently broke `REDUCE(0, some_scalar, lambda)` callers.
|
|
1612
|
+
acc = invokeLambda(lambdaVal, [(0, values_1.topLeft)(acc), (0, values_1.topLeft)(arrVal)], ctx, session);
|
|
1613
|
+
}
|
|
1614
|
+
return acc;
|
|
1615
|
+
}
|
|
1616
|
+
function evaluateSCAN(args, ctx, session) {
|
|
1617
|
+
if (args.length < 3) {
|
|
1618
|
+
return values_1.ERRORS.VALUE;
|
|
1619
|
+
}
|
|
1620
|
+
let acc = evalDeref(args[0], ctx, session);
|
|
1621
|
+
const arrVal = evalDeref(args[1], ctx, session);
|
|
1622
|
+
const lambdaVal = evalDeref(args[2], ctx, session);
|
|
1623
|
+
if (!(0, values_1.isLambda)(lambdaVal)) {
|
|
1624
|
+
return values_1.ERRORS.VALUE;
|
|
1625
|
+
}
|
|
1626
|
+
const rows = [];
|
|
1627
|
+
if (arrVal.kind === 5 /* RVKind.Array */) {
|
|
1628
|
+
for (const row of arrVal.rows) {
|
|
1629
|
+
const outRow = [];
|
|
1630
|
+
for (const cell of row) {
|
|
1631
|
+
acc = invokeLambda(lambdaVal, [(0, values_1.topLeft)(acc), cell], ctx, session);
|
|
1632
|
+
outRow.push((0, values_1.topLeft)(acc));
|
|
1633
|
+
}
|
|
1634
|
+
rows.push(outRow);
|
|
1635
|
+
}
|
|
1636
|
+
return rows.length > 0 ? (0, values_1.rvArray)(rows) : values_1.ERRORS.CALC;
|
|
1637
|
+
}
|
|
1638
|
+
// Scalar input: emit a single-cell array containing the one accumulated
|
|
1639
|
+
// value. Previously we returned #CALC! here, which was an artefact of the
|
|
1640
|
+
// array-only implementation path.
|
|
1641
|
+
const result = invokeLambda(lambdaVal, [(0, values_1.topLeft)(acc), (0, values_1.topLeft)(arrVal)], ctx, session);
|
|
1642
|
+
return (0, values_1.rvArray)([[(0, values_1.topLeft)(result)]]);
|
|
1643
|
+
}
|
|
1644
|
+
function evaluateMAKEARRAY(args, ctx, session) {
|
|
1645
|
+
if (args.length < 3) {
|
|
1646
|
+
return values_1.ERRORS.VALUE;
|
|
1647
|
+
}
|
|
1648
|
+
const rowsNum = (0, values_1.toNumberRV)((0, values_1.topLeft)(evalDeref(args[0], ctx, session)));
|
|
1649
|
+
if ((0, values_1.isError)(rowsNum)) {
|
|
1650
|
+
return rowsNum;
|
|
1651
|
+
}
|
|
1652
|
+
const colsNum = (0, values_1.toNumberRV)((0, values_1.topLeft)(evalDeref(args[1], ctx, session)));
|
|
1653
|
+
if ((0, values_1.isError)(colsNum)) {
|
|
1654
|
+
return colsNum;
|
|
1655
|
+
}
|
|
1656
|
+
const lambdaVal = evalDeref(args[2], ctx, session);
|
|
1657
|
+
if (!(0, values_1.isLambda)(lambdaVal)) {
|
|
1658
|
+
return values_1.ERRORS.VALUE;
|
|
1659
|
+
}
|
|
1660
|
+
// Truncate toward zero and reject non-positive / overflow sizes.
|
|
1661
|
+
// Without the cell-count cap the engine can silently allocate billions
|
|
1662
|
+
// of scalars before blowing the heap; matching the broadcast limit
|
|
1663
|
+
// keeps MAKEARRAY in line with the rest of the array pipeline.
|
|
1664
|
+
const rCount = Math.trunc(rowsNum.value);
|
|
1665
|
+
const cCount = Math.trunc(colsNum.value);
|
|
1666
|
+
if (rCount < 1 || cCount < 1) {
|
|
1667
|
+
return values_1.ERRORS.VALUE;
|
|
1668
|
+
}
|
|
1669
|
+
if (rCount * cCount > 10000000) {
|
|
1670
|
+
return values_1.ERRORS.NUM;
|
|
1671
|
+
}
|
|
1672
|
+
const rows = [];
|
|
1673
|
+
for (let r = 1; r <= rCount; r++) {
|
|
1674
|
+
const outRow = [];
|
|
1675
|
+
for (let c = 1; c <= cCount; c++) {
|
|
1676
|
+
outRow.push((0, values_1.topLeft)(invokeLambda(lambdaVal, [(0, values_1.rvNumber)(r), (0, values_1.rvNumber)(c)], ctx, session)));
|
|
1677
|
+
}
|
|
1678
|
+
rows.push(outRow);
|
|
1679
|
+
}
|
|
1680
|
+
return (0, values_1.rvArray)(rows);
|
|
1681
|
+
}
|
|
1682
|
+
function evaluateBYROW(args, ctx, session) {
|
|
1683
|
+
if (args.length < 2) {
|
|
1684
|
+
return values_1.ERRORS.VALUE;
|
|
1685
|
+
}
|
|
1686
|
+
const arrVal = evalDeref(args[0], ctx, session);
|
|
1687
|
+
const lambdaVal = evalDeref(args[1], ctx, session);
|
|
1688
|
+
if (!(0, values_1.isLambda)(lambdaVal)) {
|
|
1689
|
+
return values_1.ERRORS.VALUE;
|
|
1690
|
+
}
|
|
1691
|
+
if (arrVal.kind !== 5 /* RVKind.Array */) {
|
|
1692
|
+
return invokeLambda(lambdaVal, [(0, values_1.rvArray)([[(0, values_1.topLeft)(arrVal)]])], ctx, session);
|
|
1693
|
+
}
|
|
1694
|
+
const rows = [];
|
|
1695
|
+
for (const row of arrVal.rows) {
|
|
1696
|
+
const rowArr = (0, values_1.rvArray)([row.map(c => c)]);
|
|
1697
|
+
rows.push([(0, values_1.topLeft)(invokeLambda(lambdaVal, [rowArr], ctx, session))]);
|
|
1698
|
+
}
|
|
1699
|
+
return (0, values_1.rvArray)(rows);
|
|
1700
|
+
}
|
|
1701
|
+
function evaluateBYCOL(args, ctx, session) {
|
|
1702
|
+
if (args.length < 2) {
|
|
1703
|
+
return values_1.ERRORS.VALUE;
|
|
1704
|
+
}
|
|
1705
|
+
const arrVal = evalDeref(args[0], ctx, session);
|
|
1706
|
+
const lambdaVal = evalDeref(args[1], ctx, session);
|
|
1707
|
+
if (!(0, values_1.isLambda)(lambdaVal)) {
|
|
1708
|
+
return values_1.ERRORS.VALUE;
|
|
1709
|
+
}
|
|
1710
|
+
if (arrVal.kind !== 5 /* RVKind.Array */) {
|
|
1711
|
+
return invokeLambda(lambdaVal, [(0, values_1.rvArray)([[(0, values_1.topLeft)(arrVal)]])], ctx, session);
|
|
1712
|
+
}
|
|
1713
|
+
const numCols = arrVal.width;
|
|
1714
|
+
const outRow = [];
|
|
1715
|
+
for (let c = 0; c < numCols; c++) {
|
|
1716
|
+
const colArr = (0, values_1.rvArray)(arrVal.rows.map(row => [row[c]]));
|
|
1717
|
+
outRow.push((0, values_1.topLeft)(invokeLambda(lambdaVal, [colArr], ctx, session)));
|
|
1718
|
+
}
|
|
1719
|
+
return (0, values_1.rvArray)([outRow]);
|
|
1720
|
+
}
|
|
1721
|
+
// ============================================================================
|
|
1722
|
+
// Reference Functions (ROW, COLUMN, ROWS, COLUMNS)
|
|
1723
|
+
// ============================================================================
|
|
1724
|
+
/** Whether `name` is one of the four "inspect a reference's geometry" builtins. */
|
|
1725
|
+
function isSimpleRefFunction(name) {
|
|
1726
|
+
return name === "ROW" || name === "COLUMN" || name === "ROWS" || name === "COLUMNS";
|
|
1727
|
+
}
|
|
1728
|
+
function tryEvaluateRefFunction(name, args, ctx) {
|
|
1729
|
+
switch (name) {
|
|
1730
|
+
case "ROW":
|
|
1731
|
+
if (args.length === 0) {
|
|
1732
|
+
return ctx.currentAddress ? (0, values_1.rvNumber)(ctx.currentAddress.row) : values_1.ERRORS.VALUE;
|
|
1733
|
+
}
|
|
1734
|
+
if (args[0].kind === 2 /* BoundExprKind.CellRef */) {
|
|
1735
|
+
return (0, values_1.rvNumber)(args[0].row);
|
|
1736
|
+
}
|
|
1737
|
+
if (args[0].kind === 3 /* BoundExprKind.AreaRef */) {
|
|
1738
|
+
return (0, values_1.rvNumber)(args[0].top);
|
|
1739
|
+
}
|
|
1740
|
+
return undefined;
|
|
1741
|
+
case "COLUMN":
|
|
1742
|
+
if (args.length === 0) {
|
|
1743
|
+
return ctx.currentAddress ? (0, values_1.rvNumber)(ctx.currentAddress.col) : values_1.ERRORS.VALUE;
|
|
1744
|
+
}
|
|
1745
|
+
if (args[0].kind === 2 /* BoundExprKind.CellRef */) {
|
|
1746
|
+
return (0, values_1.rvNumber)(args[0].col);
|
|
1747
|
+
}
|
|
1748
|
+
if (args[0].kind === 3 /* BoundExprKind.AreaRef */) {
|
|
1749
|
+
return (0, values_1.rvNumber)(args[0].left);
|
|
1750
|
+
}
|
|
1751
|
+
return undefined;
|
|
1752
|
+
case "ROWS":
|
|
1753
|
+
if (args.length > 0 && args[0].kind === 3 /* BoundExprKind.AreaRef */) {
|
|
1754
|
+
return (0, values_1.rvNumber)(args[0].bottom - args[0].top + 1);
|
|
1755
|
+
}
|
|
1756
|
+
if (args.length > 0 && args[0].kind === 2 /* BoundExprKind.CellRef */) {
|
|
1757
|
+
return (0, values_1.rvNumber)(1);
|
|
1758
|
+
}
|
|
1759
|
+
return undefined;
|
|
1760
|
+
case "COLUMNS":
|
|
1761
|
+
if (args.length > 0 && args[0].kind === 3 /* BoundExprKind.AreaRef */) {
|
|
1762
|
+
return (0, values_1.rvNumber)(args[0].right - args[0].left + 1);
|
|
1763
|
+
}
|
|
1764
|
+
if (args.length > 0 && args[0].kind === 2 /* BoundExprKind.CellRef */) {
|
|
1765
|
+
return (0, values_1.rvNumber)(1);
|
|
1766
|
+
}
|
|
1767
|
+
return undefined;
|
|
1768
|
+
default:
|
|
1769
|
+
return undefined;
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
// ============================================================================
|
|
1773
|
+
// Reference-aware: ISREF
|
|
1774
|
+
// ============================================================================
|
|
1775
|
+
/**
|
|
1776
|
+
* ISREF(value) → TRUE if `value` is a reference; FALSE otherwise.
|
|
1777
|
+
*
|
|
1778
|
+
* Excel's rule is syntactic + runtime: any `CellRef` / `AreaRef` / 3-D ref /
|
|
1779
|
+
* `ColRangeRef` / `RowRangeRef` is a reference, and any call that *produces*
|
|
1780
|
+
* a `ReferenceValue` (INDIRECT, OFFSET) is also a reference. Errors in the
|
|
1781
|
+
* sub-expression are suppressed (Excel returns FALSE for `ISREF(INDIRECT("xx"))`
|
|
1782
|
+
* where INDIRECT returns `#REF!`).
|
|
1783
|
+
*/
|
|
1784
|
+
function evaluateISREF(args, ctx, session) {
|
|
1785
|
+
if (args.length !== 1) {
|
|
1786
|
+
return values_1.ERRORS.VALUE;
|
|
1787
|
+
}
|
|
1788
|
+
const arg = args[0];
|
|
1789
|
+
// Purely syntactic reference forms — always TRUE without evaluating.
|
|
1790
|
+
if (arg.kind === 2 /* BoundExprKind.CellRef */ ||
|
|
1791
|
+
arg.kind === 3 /* BoundExprKind.AreaRef */ ||
|
|
1792
|
+
arg.kind === 4 /* BoundExprKind.ColRangeRef */ ||
|
|
1793
|
+
arg.kind === 5 /* BoundExprKind.RowRangeRef */ ||
|
|
1794
|
+
arg.kind === 6 /* BoundExprKind.Ref3D */) {
|
|
1795
|
+
return (0, values_1.rvBoolean)(true);
|
|
1796
|
+
}
|
|
1797
|
+
// Otherwise evaluate without dereferencing. INDIRECT/OFFSET yield a
|
|
1798
|
+
// ReferenceValue when successful; anything else (error, scalar, array)
|
|
1799
|
+
// is not a reference. Per Excel, ISREF suppresses errors to FALSE.
|
|
1800
|
+
const raw = evaluate(arg, ctx, session);
|
|
1801
|
+
return (0, values_1.rvBoolean)(raw.kind === 6 /* RVKind.Reference */);
|
|
1802
|
+
}
|
|
1803
|
+
// ============================================================================
|
|
1804
|
+
// Reference-aware: CELL
|
|
1805
|
+
// ============================================================================
|
|
1806
|
+
/**
|
|
1807
|
+
* Resolve a CELL(..., ref) argument to a concrete {sheet,row,col} triple.
|
|
1808
|
+
* CELL always inspects the *top-left* cell of the referenced area.
|
|
1809
|
+
* Returns an error value if the argument cannot be resolved to a reference.
|
|
1810
|
+
*/
|
|
1811
|
+
function resolveCellRefArg(arg, ctx, session) {
|
|
1812
|
+
// Syntactic reference forms — extract top-left directly.
|
|
1813
|
+
if (arg.kind === 2 /* BoundExprKind.CellRef */) {
|
|
1814
|
+
return { sheet: arg.sheet, row: arg.row, col: arg.col };
|
|
1815
|
+
}
|
|
1816
|
+
if (arg.kind === 3 /* BoundExprKind.AreaRef */) {
|
|
1817
|
+
return { sheet: arg.sheet, row: arg.top, col: arg.left };
|
|
1818
|
+
}
|
|
1819
|
+
if (arg.kind === 4 /* BoundExprKind.ColRangeRef */) {
|
|
1820
|
+
const ws = ctx.snapshot.worksheetsByName.get(arg.sheet.toLowerCase());
|
|
1821
|
+
const top = ws?.dimensions?.top ?? 1;
|
|
1822
|
+
return { sheet: arg.sheet, row: top, col: arg.leftCol };
|
|
1823
|
+
}
|
|
1824
|
+
if (arg.kind === 5 /* BoundExprKind.RowRangeRef */) {
|
|
1825
|
+
const ws = ctx.snapshot.worksheetsByName.get(arg.sheet.toLowerCase());
|
|
1826
|
+
const left = ws?.dimensions?.left ?? 1;
|
|
1827
|
+
return { sheet: arg.sheet, row: arg.topRow, col: left };
|
|
1828
|
+
}
|
|
1829
|
+
if (arg.kind === 6 /* BoundExprKind.Ref3D */) {
|
|
1830
|
+
const first = arg.sheets[0];
|
|
1831
|
+
if (first === undefined) {
|
|
1832
|
+
return values_1.ERRORS.VALUE;
|
|
1833
|
+
}
|
|
1834
|
+
if (arg.inner.kind === 2 /* BoundExprKind.CellRef */) {
|
|
1835
|
+
return { sheet: first, row: arg.inner.row, col: arg.inner.col };
|
|
1836
|
+
}
|
|
1837
|
+
return { sheet: first, row: arg.inner.top, col: arg.inner.left };
|
|
1838
|
+
}
|
|
1839
|
+
// Fall back to evaluating — INDIRECT/OFFSET etc. may produce a ReferenceValue.
|
|
1840
|
+
const raw = evaluate(arg, ctx, session);
|
|
1841
|
+
if (raw.kind === 4 /* RVKind.Error */) {
|
|
1842
|
+
return raw;
|
|
1843
|
+
}
|
|
1844
|
+
if (raw.kind === 6 /* RVKind.Reference */ && raw.areas.length > 0) {
|
|
1845
|
+
const area = raw.areas[0];
|
|
1846
|
+
return { sheet: area.sheet, row: area.top, col: area.left };
|
|
1847
|
+
}
|
|
1848
|
+
// Non-reference argument — Excel returns #VALUE! for CELL.
|
|
1849
|
+
return values_1.ERRORS.VALUE;
|
|
1850
|
+
}
|
|
1851
|
+
/** Convert a 1-based column number to its letter form (1 → "A", 27 → "AA"). */
|
|
1852
|
+
function colNumberToLetter(colNum) {
|
|
1853
|
+
let col = "";
|
|
1854
|
+
let cv = colNum;
|
|
1855
|
+
while (cv > 0) {
|
|
1856
|
+
cv--;
|
|
1857
|
+
col = String.fromCharCode(65 + (cv % 26)) + col;
|
|
1858
|
+
cv = Math.floor(cv / 26);
|
|
1859
|
+
}
|
|
1860
|
+
return col;
|
|
1861
|
+
}
|
|
1862
|
+
/**
|
|
1863
|
+
* CELL(info_type, [reference]) — limited, workbook-internal subset.
|
|
1864
|
+
*
|
|
1865
|
+
* Supported info types:
|
|
1866
|
+
* - `"address"` → "$A$1"-style absolute reference (no sheet name)
|
|
1867
|
+
* - `"row"` → 1-based row number of the top-left cell
|
|
1868
|
+
* - `"col"` → 1-based column number of the top-left cell
|
|
1869
|
+
* - `"contents"` → value of the top-left cell
|
|
1870
|
+
* - `"type"` → "b" (blank), "l" (label/text), "v" (value/other)
|
|
1871
|
+
* - `"width"` → 8 (column width is not tracked in the snapshot)
|
|
1872
|
+
* - `"filename"` → "" (no file path available)
|
|
1873
|
+
*
|
|
1874
|
+
* Any other info type yields `#N/A`, matching Excel's treatment of
|
|
1875
|
+
* workbook-state-dependent info in contexts where the data is unavailable.
|
|
1876
|
+
*
|
|
1877
|
+
* When `reference` is omitted, the current formula's own cell is used —
|
|
1878
|
+
* if that cannot be determined, `#VALUE!` is returned.
|
|
1879
|
+
*/
|
|
1880
|
+
function evaluateCELL(args, ctx, session) {
|
|
1881
|
+
if (args.length < 1 || args.length > 2) {
|
|
1882
|
+
return values_1.ERRORS.VALUE;
|
|
1883
|
+
}
|
|
1884
|
+
// Resolve info_type — this is a plain string expression, evaluate normally.
|
|
1885
|
+
const infoRV = dereferenceValue(evaluate(args[0], ctx, session), ctx, session);
|
|
1886
|
+
if (infoRV.kind === 4 /* RVKind.Error */) {
|
|
1887
|
+
return infoRV;
|
|
1888
|
+
}
|
|
1889
|
+
const infoScalar = (0, values_1.topLeft)(infoRV);
|
|
1890
|
+
if (infoScalar.kind === 4 /* RVKind.Error */) {
|
|
1891
|
+
return infoScalar;
|
|
1892
|
+
}
|
|
1893
|
+
if (infoScalar.kind !== 2 /* RVKind.String */) {
|
|
1894
|
+
return values_1.ERRORS.VALUE;
|
|
1895
|
+
}
|
|
1896
|
+
const info = infoScalar.value.toLowerCase();
|
|
1897
|
+
// Resolve reference: explicit arg, or the current formula cell.
|
|
1898
|
+
let target;
|
|
1899
|
+
if (args.length === 2) {
|
|
1900
|
+
const resolved = resolveCellRefArg(args[1], ctx, session);
|
|
1901
|
+
if ("kind" in resolved) {
|
|
1902
|
+
return resolved;
|
|
1903
|
+
}
|
|
1904
|
+
target = resolved;
|
|
1905
|
+
}
|
|
1906
|
+
else {
|
|
1907
|
+
if (!ctx.currentAddress) {
|
|
1908
|
+
return values_1.ERRORS.VALUE;
|
|
1909
|
+
}
|
|
1910
|
+
target = {
|
|
1911
|
+
sheet: ctx.currentSheet,
|
|
1912
|
+
row: ctx.currentAddress.row,
|
|
1913
|
+
col: ctx.currentAddress.col
|
|
1914
|
+
};
|
|
1915
|
+
}
|
|
1916
|
+
switch (info) {
|
|
1917
|
+
case "address": {
|
|
1918
|
+
return (0, values_1.rvString)(`$${colNumberToLetter(target.col)}$${target.row}`);
|
|
1919
|
+
}
|
|
1920
|
+
case "row":
|
|
1921
|
+
return (0, values_1.rvNumber)(target.row);
|
|
1922
|
+
case "col":
|
|
1923
|
+
case "column":
|
|
1924
|
+
return (0, values_1.rvNumber)(target.col);
|
|
1925
|
+
case "contents": {
|
|
1926
|
+
return getCellValue(target.sheet, target.row, target.col, ctx, session);
|
|
1927
|
+
}
|
|
1928
|
+
case "type": {
|
|
1929
|
+
const val = getCellValue(target.sheet, target.row, target.col, ctx, session);
|
|
1930
|
+
if (val.kind === 0 /* RVKind.Blank */) {
|
|
1931
|
+
return (0, values_1.rvString)("b");
|
|
1932
|
+
}
|
|
1933
|
+
if (val.kind === 2 /* RVKind.String */) {
|
|
1934
|
+
return (0, values_1.rvString)("l");
|
|
1935
|
+
}
|
|
1936
|
+
// Numbers, booleans, errors — all classified as "value".
|
|
1937
|
+
return (0, values_1.rvString)("v");
|
|
1938
|
+
}
|
|
1939
|
+
case "width":
|
|
1940
|
+
// Column width is not captured in the snapshot — return Excel's default.
|
|
1941
|
+
return (0, values_1.rvNumber)(8);
|
|
1942
|
+
case "filename":
|
|
1943
|
+
// No file path is available to the calculation engine.
|
|
1944
|
+
return (0, values_1.rvString)("");
|
|
1945
|
+
default:
|
|
1946
|
+
return values_1.ERRORS.NA;
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
// ============================================================================
|
|
1950
|
+
// Name Expression
|
|
1951
|
+
// ============================================================================
|
|
1952
|
+
function evaluateNameExpr(expr, ctx, session) {
|
|
1953
|
+
// Check local bindings first (LET variables)
|
|
1954
|
+
if (ctx.localBindings?.has(expr.upperName)) {
|
|
1955
|
+
return ctx.localBindings.get(expr.upperName);
|
|
1956
|
+
}
|
|
1957
|
+
// Try snapshot defined name resolution (for formula-based names).
|
|
1958
|
+
// Respects scope precedence: sheet-local > workbook-global.
|
|
1959
|
+
const dn = (0, workbook_snapshot_1.resolveDefinedName)(ctx.snapshot.definedNames, expr.name, ctx.currentSheet);
|
|
1960
|
+
if (dn && dn.ranges.length > 0) {
|
|
1961
|
+
if (dn.ranges.length > 1) {
|
|
1962
|
+
return values_1.ERRORS.VALUE;
|
|
1963
|
+
}
|
|
1964
|
+
const rangeStr = dn.ranges[0];
|
|
1965
|
+
const parsed = (0, address_utils_1.parseDefinedNameRange)(rangeStr);
|
|
1966
|
+
if (parsed) {
|
|
1967
|
+
if (parsed.startRow === parsed.endRow && parsed.startCol === parsed.endCol) {
|
|
1968
|
+
return getCellValue(parsed.sheet, parsed.startRow, parsed.startCol, ctx, session);
|
|
1969
|
+
}
|
|
1970
|
+
return buildRangeArray(ctx, session, parsed.sheet, Math.min(parsed.startRow, parsed.endRow), Math.min(parsed.startCol, parsed.endCol), Math.max(parsed.startRow, parsed.endRow), Math.max(parsed.startCol, parsed.endCol));
|
|
1971
|
+
}
|
|
1972
|
+
// Formula expression — parse and evaluate via shared helper
|
|
1973
|
+
const nameResult = evaluateFormulaName(expr.upperName, rangeStr, ctx, session);
|
|
1974
|
+
return nameResult ?? values_1.ERRORS.NAME;
|
|
1975
|
+
}
|
|
1976
|
+
return values_1.ERRORS.NAME;
|
|
1977
|
+
}
|
|
1978
|
+
// ============================================================================
|
|
1979
|
+
// Formula-based Defined Name Evaluation (shared helper)
|
|
1980
|
+
// ============================================================================
|
|
1981
|
+
/**
|
|
1982
|
+
* Evaluate a formula-based defined name expression, with caching.
|
|
1983
|
+
* Cache key includes name + sheet + cell address to handle position-dependent
|
|
1984
|
+
* formulas like ROW()/COLUMN().
|
|
1985
|
+
*
|
|
1986
|
+
* Returns the evaluated result, or undefined if parsing/evaluation fails.
|
|
1987
|
+
*/
|
|
1988
|
+
function evaluateFormulaName(upperName, formulaExpr, ctx, session) {
|
|
1989
|
+
const addr = ctx.currentAddress;
|
|
1990
|
+
const cacheKey = addr
|
|
1991
|
+
? `__NAME__${upperName}__${ctx.currentSheet}__${addr.row}:${addr.col}`
|
|
1992
|
+
: `__NAME__${upperName}__${ctx.currentSheet}`;
|
|
1993
|
+
const cached = session.nameCache.get(cacheKey);
|
|
1994
|
+
if (cached !== undefined) {
|
|
1995
|
+
return cached;
|
|
1996
|
+
}
|
|
1997
|
+
// Guard against recursion through a defined name that references itself.
|
|
1998
|
+
// Uses a dedicated prefix so it cannot collide with formula-cell guard keys.
|
|
1999
|
+
const guardKey = `__NAMEEVAL__${upperName}`;
|
|
2000
|
+
if (session.evaluating.has(guardKey)) {
|
|
2001
|
+
return values_1.ERRORS.CALC;
|
|
2002
|
+
}
|
|
2003
|
+
session.evaluating.add(guardKey);
|
|
2004
|
+
try {
|
|
2005
|
+
const tokens = (0, tokenizer_1.tokenize)(formulaExpr);
|
|
2006
|
+
const ast = (0, parser_1.parse)(tokens);
|
|
2007
|
+
const bindCtx = { snapshot: ctx.snapshot, currentSheet: ctx.currentSheet };
|
|
2008
|
+
const bound = (0, binder_1.bind)(ast, bindCtx);
|
|
2009
|
+
const result = evaluate(bound, ctx, session);
|
|
2010
|
+
session.nameCache.set(cacheKey, result);
|
|
2011
|
+
return result;
|
|
2012
|
+
}
|
|
2013
|
+
catch {
|
|
2014
|
+
return undefined;
|
|
2015
|
+
}
|
|
2016
|
+
finally {
|
|
2017
|
+
session.evaluating.delete(guardKey);
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
// ============================================================================
|
|
2021
|
+
// Lambda Expression
|
|
2022
|
+
// ============================================================================
|
|
2023
|
+
function evaluateLambdaExpr(expr, ctx) {
|
|
2024
|
+
return (0, values_1.rvLambda)([...expr.params], expr.body, ctx.localBindings ? new Map(ctx.localBindings) : undefined);
|
|
2025
|
+
}
|
|
2026
|
+
// ============================================================================
|
|
2027
|
+
// Structured Reference (runtime resolution)
|
|
2028
|
+
// ============================================================================
|
|
2029
|
+
function evaluateStructuredRef(expr, ctx, session) {
|
|
2030
|
+
const snapshot = ctx.snapshot;
|
|
2031
|
+
const addr = ctx.currentAddress;
|
|
2032
|
+
// Find the table
|
|
2033
|
+
let tableName = expr.tableName;
|
|
2034
|
+
let tableSheet = null;
|
|
2035
|
+
let tableInfo = null;
|
|
2036
|
+
if (tableName === "") {
|
|
2037
|
+
// Implicit table — find the table containing the current cell
|
|
2038
|
+
if (!addr) {
|
|
2039
|
+
return values_1.ERRORS.REF;
|
|
2040
|
+
}
|
|
2041
|
+
// Sheet names are case-insensitive in Excel. Comparing the literal
|
|
2042
|
+
// `ws.name !== addr.sheet` would miss tables when the formula-cell's
|
|
2043
|
+
// address records its sheet in a different case than the workbook's
|
|
2044
|
+
// canonical name (possible after rename / import flows).
|
|
2045
|
+
const addrSheetLower = addr.sheet.toLowerCase();
|
|
2046
|
+
for (const ws of snapshot.worksheets) {
|
|
2047
|
+
if (ws.name.toLowerCase() !== addrSheetLower) {
|
|
2048
|
+
continue;
|
|
2049
|
+
}
|
|
2050
|
+
for (const t of ws.tables) {
|
|
2051
|
+
const g = (0, structured_ref_utils_1.buildTableGeometry)(t);
|
|
2052
|
+
const width = t.columns.length;
|
|
2053
|
+
if (addr.row >= g.dataRowStart &&
|
|
2054
|
+
addr.row <= g.dataRowEnd &&
|
|
2055
|
+
addr.col >= t.topLeft.col &&
|
|
2056
|
+
addr.col < t.topLeft.col + width) {
|
|
2057
|
+
tableInfo = t;
|
|
2058
|
+
tableSheet = ws.name;
|
|
2059
|
+
tableName = t.name;
|
|
2060
|
+
break;
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
if (tableInfo) {
|
|
2064
|
+
break;
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
else {
|
|
2069
|
+
// Named table — use the pre-built index for O(1) lookup
|
|
2070
|
+
const resolved = snapshot.tablesByName.get(tableName.toLowerCase());
|
|
2071
|
+
if (resolved) {
|
|
2072
|
+
tableInfo = resolved.table;
|
|
2073
|
+
tableSheet = resolved.sheetName;
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
if (!tableInfo || !tableSheet) {
|
|
2077
|
+
return values_1.ERRORS.REF;
|
|
2078
|
+
}
|
|
2079
|
+
const geo = (0, structured_ref_utils_1.buildTableGeometry)(tableInfo);
|
|
2080
|
+
// Strict column resolution — unknown column names surface as #REF!
|
|
2081
|
+
const colRange = (0, structured_ref_utils_1.resolveStructuredRefColumns)(expr.columns, tableInfo, "strict");
|
|
2082
|
+
if (colRange === "error") {
|
|
2083
|
+
return values_1.ERRORS.REF;
|
|
2084
|
+
}
|
|
2085
|
+
const rowRange = (0, structured_ref_utils_1.resolveStructuredRefRows)(expr.specials, geo);
|
|
2086
|
+
let rowTop;
|
|
2087
|
+
let rowBottom;
|
|
2088
|
+
if (rowRange === "error") {
|
|
2089
|
+
return values_1.ERRORS.REF;
|
|
2090
|
+
}
|
|
2091
|
+
else if (rowRange === "thisRow") {
|
|
2092
|
+
if (addr) {
|
|
2093
|
+
rowTop = addr.row;
|
|
2094
|
+
rowBottom = addr.row;
|
|
2095
|
+
}
|
|
2096
|
+
else {
|
|
2097
|
+
return values_1.ERRORS.VALUE;
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
else {
|
|
2101
|
+
rowTop = rowRange.rowTop;
|
|
2102
|
+
rowBottom = rowRange.rowBottom;
|
|
2103
|
+
}
|
|
2104
|
+
// Single cell — return as single-cell ReferenceValue
|
|
2105
|
+
if (rowTop === rowBottom && colRange.colLeft === colRange.colRight) {
|
|
2106
|
+
return (0, values_1.rvCellRef)(tableSheet, rowTop, colRange.colLeft);
|
|
2107
|
+
}
|
|
2108
|
+
// Range — return as area ReferenceValue
|
|
2109
|
+
return (0, values_1.rvRef)(tableSheet, rowTop, colRange.colLeft, rowBottom, colRange.colRight);
|
|
2110
|
+
}
|
|
2111
|
+
// ============================================================================
|
|
2112
|
+
// Array Literal
|
|
2113
|
+
// ============================================================================
|
|
2114
|
+
function evaluateArrayLiteral(expr, ctx, session) {
|
|
2115
|
+
const rows = [];
|
|
2116
|
+
for (const row of expr.rows) {
|
|
2117
|
+
const evalRow = [];
|
|
2118
|
+
for (const elem of row) {
|
|
2119
|
+
evalRow.push((0, values_1.topLeft)(evalDeref(elem, ctx, session)));
|
|
2120
|
+
}
|
|
2121
|
+
rows.push(evalRow);
|
|
2122
|
+
}
|
|
2123
|
+
return (0, values_1.rvArray)(rows);
|
|
2124
|
+
}
|
|
2125
|
+
// ============================================================================
|
|
2126
|
+
// Implicit Intersection
|
|
2127
|
+
// ============================================================================
|
|
2128
|
+
function implicitIntersect(val, ctx) {
|
|
2129
|
+
if ((0, values_1.isScalar)(val)) {
|
|
2130
|
+
return val;
|
|
2131
|
+
}
|
|
2132
|
+
if (val.kind === 7 /* RVKind.Lambda */) {
|
|
2133
|
+
return val;
|
|
2134
|
+
}
|
|
2135
|
+
if (val.kind === 6 /* RVKind.Reference */) {
|
|
2136
|
+
// Implicit intersection on a reference: resolve using formula cell's row/col
|
|
2137
|
+
if (val.areas.length === 0) {
|
|
2138
|
+
return values_1.BLANK;
|
|
2139
|
+
}
|
|
2140
|
+
const area = val.areas[0];
|
|
2141
|
+
const isSingleCell = area.top === area.bottom && area.left === area.right;
|
|
2142
|
+
if (isSingleCell) {
|
|
2143
|
+
return val; // Single cell ref — keep as-is, will be dereferenced at use site
|
|
2144
|
+
}
|
|
2145
|
+
// Multi-cell reference — apply implicit intersection
|
|
2146
|
+
const addr = ctx.currentAddress;
|
|
2147
|
+
if (!addr) {
|
|
2148
|
+
return val;
|
|
2149
|
+
}
|
|
2150
|
+
// Single column — pick row
|
|
2151
|
+
if (area.left === area.right && addr.row >= area.top && addr.row <= area.bottom) {
|
|
2152
|
+
return (0, values_1.rvCellRef)(area.sheet, addr.row, area.left);
|
|
2153
|
+
}
|
|
2154
|
+
// Single row — pick column
|
|
2155
|
+
if (area.top === area.bottom && addr.col >= area.left && addr.col <= area.right) {
|
|
2156
|
+
return (0, values_1.rvCellRef)(area.sheet, area.top, addr.col);
|
|
2157
|
+
}
|
|
2158
|
+
// Both row and col
|
|
2159
|
+
if (addr.row >= area.top &&
|
|
2160
|
+
addr.row <= area.bottom &&
|
|
2161
|
+
addr.col >= area.left &&
|
|
2162
|
+
addr.col <= area.right) {
|
|
2163
|
+
return (0, values_1.rvCellRef)(area.sheet, addr.row, addr.col);
|
|
2164
|
+
}
|
|
2165
|
+
return val;
|
|
2166
|
+
}
|
|
2167
|
+
if (val.kind !== 5 /* RVKind.Array */) {
|
|
2168
|
+
return val;
|
|
2169
|
+
}
|
|
2170
|
+
const arr = val;
|
|
2171
|
+
if (arr.height === 0 || arr.width === 0) {
|
|
2172
|
+
return values_1.BLANK;
|
|
2173
|
+
}
|
|
2174
|
+
if (arr.height === 1 && arr.width === 1) {
|
|
2175
|
+
return arr.rows[0][0];
|
|
2176
|
+
}
|
|
2177
|
+
const addr = ctx.currentAddress;
|
|
2178
|
+
if (!addr) {
|
|
2179
|
+
return arr.rows[0][0];
|
|
2180
|
+
}
|
|
2181
|
+
// Single row — pick column by offset
|
|
2182
|
+
if (arr.height === 1) {
|
|
2183
|
+
if (arr.originCol !== undefined) {
|
|
2184
|
+
const colIdx = addr.col - arr.originCol;
|
|
2185
|
+
if (colIdx >= 0 && colIdx < arr.width) {
|
|
2186
|
+
return arr.rows[0][colIdx];
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
return arr.rows[0][0];
|
|
2190
|
+
}
|
|
2191
|
+
// Single column — pick row by offset
|
|
2192
|
+
if (arr.width === 1) {
|
|
2193
|
+
if (arr.originRow !== undefined) {
|
|
2194
|
+
const rowIdx = addr.row - arr.originRow;
|
|
2195
|
+
if (rowIdx >= 0 && rowIdx < arr.height) {
|
|
2196
|
+
return arr.rows[rowIdx][0];
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
return arr.rows[0][0];
|
|
2200
|
+
}
|
|
2201
|
+
// Multi-row, multi-column
|
|
2202
|
+
if (arr.originRow !== undefined && arr.originCol !== undefined) {
|
|
2203
|
+
const rowIdx = addr.row - arr.originRow;
|
|
2204
|
+
const colIdx = addr.col - arr.originCol;
|
|
2205
|
+
if (rowIdx >= 0 && rowIdx < arr.height && colIdx >= 0 && colIdx < arr.width) {
|
|
2206
|
+
return arr.rows[rowIdx][colIdx];
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
return arr.rows[0][0];
|
|
2210
|
+
}
|
|
2211
|
+
// ============================================================================
|
|
2212
|
+
// R1C1 Reference Resolution
|
|
2213
|
+
// ============================================================================
|
|
2214
|
+
function resolveR1C1(refText, ctx, session) {
|
|
2215
|
+
const upper = refText.toUpperCase().trim();
|
|
2216
|
+
// Check for range separator
|
|
2217
|
+
let depth = 0;
|
|
2218
|
+
let sepIdx = -1;
|
|
2219
|
+
for (let i = 0; i < upper.length; i++) {
|
|
2220
|
+
if (upper[i] === "[") {
|
|
2221
|
+
depth++;
|
|
2222
|
+
}
|
|
2223
|
+
else if (upper[i] === "]") {
|
|
2224
|
+
depth--;
|
|
2225
|
+
}
|
|
2226
|
+
else if (upper[i] === ":" && depth === 0) {
|
|
2227
|
+
sepIdx = i;
|
|
2228
|
+
break;
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
if (sepIdx !== -1) {
|
|
2232
|
+
const s = parseR1C1Single(upper.slice(0, sepIdx), ctx);
|
|
2233
|
+
const e = parseR1C1Single(upper.slice(sepIdx + 1), ctx);
|
|
2234
|
+
if (!s || !e) {
|
|
2235
|
+
return values_1.ERRORS.REF;
|
|
2236
|
+
}
|
|
2237
|
+
const top = Math.min(s.row, e.row);
|
|
2238
|
+
const bottom = Math.max(s.row, e.row);
|
|
2239
|
+
const left = Math.min(s.col, e.col);
|
|
2240
|
+
const right = Math.max(s.col, e.col);
|
|
2241
|
+
return (0, values_1.rvRef)(ctx.currentSheet, top, left, bottom, right);
|
|
2242
|
+
}
|
|
2243
|
+
const ref = parseR1C1Single(upper, ctx);
|
|
2244
|
+
if (!ref || ref.row < 1 || ref.col < 1) {
|
|
2245
|
+
return values_1.ERRORS.REF;
|
|
2246
|
+
}
|
|
2247
|
+
return (0, values_1.rvCellRef)(ctx.currentSheet, ref.row, ref.col);
|
|
2248
|
+
}
|
|
2249
|
+
function parseR1C1Single(text, ctx) {
|
|
2250
|
+
const re = /^R(\[(-?\d+)\]|(\d+))C(\[(-?\d+)\]|(\d+))$/;
|
|
2251
|
+
const m = re.exec(text);
|
|
2252
|
+
if (!m) {
|
|
2253
|
+
return null;
|
|
2254
|
+
}
|
|
2255
|
+
const addr = ctx.currentAddress;
|
|
2256
|
+
const row = m[2] !== undefined ? (addr?.row ?? 1) + parseInt(m[2], 10) : parseInt(m[3], 10);
|
|
2257
|
+
const col = m[5] !== undefined ? (addr?.col ?? 1) + parseInt(m[5], 10) : parseInt(m[6], 10);
|
|
2258
|
+
return { row, col };
|
|
2259
|
+
}
|
|
2260
|
+
// ============================================================================
|
|
2261
|
+
// Helpers
|
|
2262
|
+
// ============================================================================
|
|
2263
|
+
/**
|
|
2264
|
+
* Evaluate a BoundExpr and dereference any resulting ReferenceValue.
|
|
2265
|
+
* Use this whenever a concrete (non-reference) value is needed.
|
|
2266
|
+
*/
|
|
2267
|
+
function evalDeref(expr, ctx, session) {
|
|
2268
|
+
return dereferenceValue(evaluate(expr, ctx, session), ctx, session);
|
|
2269
|
+
}
|
|
2270
|
+
function resolveLambdaName(name, args, ctx, session) {
|
|
2271
|
+
// Check local bindings
|
|
2272
|
+
if (ctx.localBindings?.has(name)) {
|
|
2273
|
+
const val = ctx.localBindings.get(name);
|
|
2274
|
+
if ((0, values_1.isLambda)(val)) {
|
|
2275
|
+
return invokeLambda(val, args, ctx, session);
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
2278
|
+
// Check defined names that resolve to lambdas (via snapshot, scope-aware)
|
|
2279
|
+
const dn = (0, workbook_snapshot_1.resolveDefinedName)(ctx.snapshot.definedNames, name, ctx.currentSheet);
|
|
2280
|
+
if (dn && dn.ranges.length === 1) {
|
|
2281
|
+
const rangeStr = dn.ranges[0];
|
|
2282
|
+
const parsed = (0, address_utils_1.parseDefinedNameRange)(rangeStr);
|
|
2283
|
+
if (parsed && parsed.startRow === parsed.endRow && parsed.startCol === parsed.endCol) {
|
|
2284
|
+
const cellVal = getCellValue(parsed.sheet, parsed.startRow, parsed.startCol, ctx, session);
|
|
2285
|
+
if ((0, values_1.isLambda)(cellVal)) {
|
|
2286
|
+
return invokeLambda(cellVal, args, ctx, session);
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
// Formula-based name — evaluate via shared helper
|
|
2290
|
+
if (!parsed) {
|
|
2291
|
+
const nameVal = evaluateFormulaName(name.toUpperCase(), rangeStr, ctx, session);
|
|
2292
|
+
if (nameVal !== undefined && (0, values_1.isLambda)(nameVal)) {
|
|
2293
|
+
return invokeLambda(nameVal, args, ctx, session);
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
return undefined;
|
|
2298
|
+
}
|