@sheetxl/primitives 0.8.0-beta.1 → 0.8.0-beta.3

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.
@@ -1,4 +1,5 @@
1
1
  import { Scalar } from './Scalar';
2
+ import { ValueKind } from './ValueKind';
2
3
  import { IRange } from './IRange';
3
4
  import { Lambda } from './Lambda';
4
5
  /**
@@ -13,7 +14,7 @@ import { Lambda } from './Lambda';
13
14
  * - What was the value BEFORE the parameter's type coerced it?
14
15
  * - Was that empty slot an omission, or a blank cell the caller pointed at?
15
16
  *
16
- * Excel has measured behaviors that turn on exactly those questions, and they cluster at the
17
+ * Excel has behaviors that turn on exactly those questions, and they cluster at the
17
18
  * parameters Excel declares `any` — where there is nothing to express as a type, because Excel
18
19
  * declined to express one. `CUMIPMT` refuses a boolean at every slot while `FV` coerces one at
19
20
  * every slot; the difference is not the values, it is that `CUMIPMT`'s slots are `any` and its
@@ -23,8 +24,8 @@ import { Lambda } from './Lambda';
23
24
  *
24
25
  * | question | answers |
25
26
  * | --- | --- |
26
- * | *what was supplied* | {@link getLength}, {@link isOmittedAt} |
27
- * | *what it is* | {@link getRawAt} |
27
+ * | *what was supplied* | {@link getLength}, {@link getKindAt} |
28
+ * | *what it is* | {@link getRawAt}, {@link getKindAt} |
28
29
  *
29
30
  * Coercion is deliberately absent. A body that wants a number calls
30
31
  * {@link IFormulaContext.parseAsNumber} on the value this hands it — that already takes a value,
@@ -40,7 +41,7 @@ import { Lambda } from './Lambda';
40
41
  * ```typescript
41
42
  * export function CUMIPMT(rate: Scalar, nper: Scalar): number {
42
43
  * const args = FormulaContext.getArgs();
43
- * if (args.isOmittedAt(0)) return FormulaError.BuiltIn.NA; // `CUMIPMT(,120,…)` is #N/A
44
+ * if (args.getKindAt(0) === ValueKind.Omitted) return FormulaError.BuiltIn.NA; // `CUMIPMT(,120,…)`
44
45
  * const asNumber = FormulaContext.parseAsNumber(args.getRawAt(0) as Scalar); // TRUE is #VALUE!
45
46
  * // …
46
47
  * }
@@ -88,6 +89,10 @@ export interface IFunctionArguments {
88
89
  * type, the member has already been converted by that declaration, so the raw and coerced answers
89
90
  * coincide there. At the `Scalar` slots this interface exists for, nothing coerces and the value
90
91
  * is the member exactly as the caller's range holds it.
92
+ *
93
+ * A reference UNION (`(A1:A3,C1:C3)`) is an array too — one `IReferenceRange` per area — which
94
+ * is how a body whose slot took the areas apart can still tell one argument's union from two
95
+ * arguments (`SUMPRODUCT` refuses the first and accepts the second).
91
96
  */
92
97
  getRawAt(index: number): IFunctionArguments.RawArg | IFunctionArguments.RawArg[];
93
98
  /**
@@ -106,22 +111,65 @@ export interface IFunctionArguments {
106
111
  */
107
112
  getRepeatingArgsAt(index: number): IFunctionArguments;
108
113
  /**
109
- * True for an OMITTED argument — one the caller supplied EMPTY, as the trailing comma of
110
- * `CUMIPMT(,120,100000,13,24,0)`.
114
+ * The KIND of the argument at `index` — the total discriminator over {@link getRawAt}'s value.
115
+ *
116
+ * @param index The PARAMETER index — the position in the body's own signature.
111
117
  *
112
118
  * @remarks
113
- * There are THREE positions, not two, and Excel answers each differently:
119
+ * Every raw argument is exactly one of a closed set of kinds ({@link ValueKind}), and this
120
+ * answers without materializing anything — a reference stays coords underneath. The scalar
121
+ * kinds ARE `ScalarType` values, so `getKindAt(i) === ScalarType.Boolean` reads naturally.
122
+ *
123
+ * Under a lift this reports THIS invocation's value, like {@link getRawAt} — except the
124
+ * {@link ValueKind.Omitted} answer, which is a fact about the CALL (the caller wrote an empty
125
+ * comma) and does not change per broadcast position. At a REPEATING parameter it answers for
126
+ * the group's FIRST argument; per-argument kinds are one level down —
127
+ * `getRepeatingArgsAt(i).getKindAt(j)`.
128
+ *
129
+ * There are THREE argument positions:
114
130
  *
115
- * | form | position | Excel |
131
+ * | form | position | answer |
116
132
  * | --- | --- | --- |
117
- * | `CUMIPMT(0.01,120,100000,13,24)` | ABSENT | refused at entry |
118
- * | `CUMIPMT(,120,100000,13,24,0)` | **OMITTED** | `#N/A` |
119
- * | `CUMIPMT(0.01,120,100000,13,24,E1)`, E1 blank | a blank CELL | the ordinary answer |
133
+ * | `CUMIPMT(0.01,120,100000,13,24)` | never wrote a 6th | `Omitted` here — the POSITION fact is {@link getLength} (`index >= getLength()`), not a kind |
134
+ * | `CUMIPMT(,120,100000,13,24,0)` | the empty comma | {@link ValueKind.Omitted} |
135
+ * | `CUMIPMT(…,E1)`, E1 blank | a blank CELL | {@link ValueKind.Reference} — blankness is the cell's value, not the argument |
136
+ *
137
+ * The kind is the argument's own and it survives what it passes through:
138
+ * `VLOOKUP(10,10,1,TRUE)` is `#N/A` where `VLOOKUP(10,{10},1,TRUE)` is 10 — scalar vs 1x1
139
+ * array; `TYPE(IF(TRUE,10))` is 1 where `TYPE(IF(TRUE,{10}))` is 64 — a selector carries the
140
+ * kind through in both directions.
141
+ */
142
+ getKindAt(index: number): ValueKind;
143
+ /**
144
+ * True when the argument at `index` is DIRECT — Excel's "typed into the list of arguments":
145
+ * the evaluated value IS a value, not a container or a callable.
146
+ *
147
+ * @param index The PARAMETER index — the position in the body's own signature.
148
+ *
149
+ * @remarks
150
+ * The derived predicate over {@link getKindAt}: true exactly when the kind is one of the
151
+ * `ScalarType` members. A blank (`ScalarType.Null`) IS a value and is direct; an omitted
152
+ * argument, a reference, an array and a callable are not. Directness is what the
153
+ * direct-vs-member coercion rules key on (`SUM(1,TRUE)` is 2 where `SUM({1,TRUE})` is 1), so
154
+ * a body speaking that vocabulary can ask in it.
155
+ */
156
+ isDirectAt(index: number): boolean;
157
+ /**
158
+ * How many AREAS the argument at `index` covers — 1 for an ordinary reference, more for a
159
+ * union (`(A1:A5,B1:B2)`), and 0 for anything that is not a reference at all.
160
+ *
161
+ * @param index The PARAMETER index — the position in the body's own signature.
162
+ *
163
+ * @remarks
164
+ * {@link getKindAt} deliberately cannot answer this: a union IS a reference and reports
165
+ * {@link ValueKind.Reference}, the same as `A1:A5`. This is the question a body asks when the
166
+ * union is the thing it must decide about, and the three possible answers all come from asking
167
+ * it — flatten every area, refuse, or `#REF!`.
120
168
  *
121
- * This separates the middle row from the other two. ABSENT is `index >= getLength()`; a blank
122
- * cell is an ordinary value the caller pointed at and is not omitted.
169
+ * Only a {@link IFunction.ParameterTypeJSON.reportType} slot can see a union at all; at every
170
+ * other slot the marshaller has already answered `#VALUE!`.
123
171
  */
124
- isOmittedAt(index: number): boolean;
172
+ getAreaCountAt(index: number): number;
125
173
  }
126
174
  /**
127
175
  * {@inheritDoc IFunctionArguments}
@@ -136,7 +184,8 @@ export declare namespace IFunctionArguments {
136
184
  * One argument as the caller wrote it — a value, a container, a callable, or nothing.
137
185
  *
138
186
  * @remarks
139
- * `undefined` is an OMITTED or ABSENT argument; `null` is a blank, which is a value.
187
+ * `undefined` is an OMITTED or ABSENT argument; `null` is a blank, which is a value. A reference
188
+ * union is delivered as `RawArg[]` — one reference per area (see {@link IFunctionArguments.getRawAt}).
140
189
  */
141
190
  type RawArg = Scalar | IRange | Lambda | undefined;
142
191
  }
@@ -69,11 +69,16 @@ export interface IRange<T extends Scalar = Scalar> {
69
69
  uniqueIndices(axis?: IRange.Axis, exactlyOnce?: boolean): number[];
70
70
  /**
71
71
  * Applies a callback function to elements from this range and another range or a scalar,
72
- * using NumPy-style broadcasting.
72
+ * using Excel's broadcasting rules. This is the two-operand special case of {@link zip}:
73
+ * `a.broadcast(b, f)` is `a.zip([b], ([x, y]) => f(x, y))`.
73
74
  *
74
- * **Broadcasting Rules:**
75
- * - If `other` is a scalar, it is broadcasted to match the shape of this range.
76
- * - If `other` is a range, both ranges must have the same length.
75
+ * **Broadcasting Rules** (the same as {@link zip}):
76
+ * - A scalar or a 1×1 range fills any shape: (1×1) + (3×4) → (3×4)
77
+ * - Same shapes pair position by position: (3×4) + (3×4) → (3×4)
78
+ * - A dimension of 1 tiles across the other operand's: (3×1) + (3×4) → (3×4)
79
+ * - Incompatible shapes (neither equal nor 1) throw, unless `misalignValue` is set, in which
80
+ * case the result is the union shape and every position outside the intersection holds
81
+ * `misalignValue`. Here the pad is a RESULT; in {@link zip} it is an ARGUMENT.
77
82
  *
78
83
  * @param other The other range or scalar to broadcast with.
79
84
  * @param callbackFn The function to apply to corresponding elements.
@@ -82,11 +87,10 @@ export interface IRange<T extends Scalar = Scalar> {
82
87
  * @returns A new range with the broadcasted results.
83
88
  *
84
89
  * @remarks
85
- * Broadcasting rules (NumPy-style):
86
- * - Scalar broadcasts to any shape: (1×1) + (3×4) → (3×4)
87
- * - Same shapes work: (3×4) + (3×4) → (3×4)
88
- * - Trailing dimensions of 1 broadcast: (3×1) + (3×4) → (3×4)
89
- * - Incompatible shapes error: (3×4) + (2×3) → Error (unless misalignValue set)
90
+ * Iteration is sparse: the positions visited are the union of both operands' stored cells. A
91
+ * position empty on both sides has one answer, and a callback that reads only its two values
92
+ * (`callbackFn.length === 2`) receives that answer computed ONCE and written as runs; a callback
93
+ * that asks for `coords` or `range` is called per position instead.
90
94
  */
91
95
  broadcast<TS extends Scalar = T>(other: IRange | TS, callbackFn: (a: TS, b: TS, coords: IRange.CellCoords, range: IRange<T>) => TS, options?: IRange.BroadcastOptions): this;
92
96
  /**
@@ -243,6 +247,18 @@ export interface IRange<T extends Scalar = Scalar> {
243
247
  * If the callback returns an error this will propagate the error as the value with the error.
244
248
  */
245
249
  filter<TS extends Scalar = T>(callbackFn: (value: TS, coords: IRange.CellCoords, range: IRange<T>) => MaybeError<boolean>, options?: IRange.TraversalAxis | IRange.IteratorOptions): this;
250
+ /**
251
+ * The first error member in row-major order, or `null` when there is none — the identity that
252
+ * propagates when a body refuses a range with an error inside it:
253
+ * `if (range.hasError()) return range.firstError();`.
254
+ *
255
+ * @remarks
256
+ * Errors INSIDE a range are the body's to judge: the engine hands a valid range over whole and
257
+ * never looks inside, so a function whose answer depends on every member says so with this
258
+ * line, placed before its own validation where Excel's error outranks it. A reshaper or a
259
+ * lookup, whose members pass through or are never read, needs no line at all.
260
+ */
261
+ firstError(): FormulaError.Known | null;
246
262
  /**
247
263
  * Linearizes the range into a single column by flattened ordinal — the member at ordinal `i`
248
264
  * lands at row `i`, column 0.
@@ -353,6 +369,20 @@ export interface IRange<T extends Scalar = Scalar> {
353
369
  * `flatten` keeps them as holes in a range.
354
370
  */
355
371
  getValuesFlat<TS extends Scalar = T>(options?: IRange.IteratorOptions): TS[];
372
+ /**
373
+ * Returns the position of the first member equal to `value`, or `-1` when there is none.
374
+ *
375
+ * @param value The value to find.
376
+ * @param options {@link IRange.IndexOfOptions}
377
+ *
378
+ * @remarks
379
+ * Equality is the `=` operator's: text is case-insensitive, and no value matches one of another
380
+ * type (`1` does not match `"1"`, nor `TRUE` `1`). Empty members, errors and rich data are never
381
+ * a match. The position is the offset along a row or column vector, and the row-major offset
382
+ * within a larger range. This is `MATCH(value, range, 0)` less one, and a range read by many
383
+ * lookups is indexed once rather than scanned per call.
384
+ */
385
+ indexOf(value: Scalar, options?: IRange.IndexOfOptions): number;
356
386
  /**
357
387
  * Returns the number of columns of the range.
358
388
  */
@@ -372,7 +402,7 @@ export interface IRange<T extends Scalar = Scalar> {
372
402
  * outputs and `map` cannot know what type they are. Use {@link IRange.toType} on the result if
373
403
  * you do know. (`options.type` still applies to the values handed TO the callback.)
374
404
  */
375
- map<TS extends Scalar = T>(callbackFn: (value: TS, coords: IRange.CellCoords, range: IRange<T>) => TS, options?: IRange.IteratorOptions): this;
405
+ map<TS extends Scalar = T>(callbackFn: (value: TS, coords: IRange.CellCoords, range: IRange<T>) => TS, options?: IRange.MapOptions): this;
376
406
  /**
377
407
  * Returns a new range where each cell is replaced with the value from `ifTrues`
378
408
  * where the corresponding `conditions` value is strictly `true`.
@@ -629,40 +659,54 @@ export interface IRange<T extends Scalar = Scalar> {
629
659
  */
630
660
  values<TS extends Scalar = T>(options?: IRange.IteratorOptions): IterableIterator<TS>;
631
661
  /**
632
- * Iterates over this range and one or more other ranges or scalars in lockstep,
633
- * calling the provided function with the broadcast-resolved values at each position,
634
- * and returns a new range containing the results.
662
+ * Aligns this range with any number of other ranges or scalars under Excel's broadcasting
663
+ * rules, calls `callbackFn` with the aligned values at each position, and returns the results as
664
+ * a new range of the broadcast shape. This is the N-ary form of {@link broadcast}, and the numpy
665
+ * idiom "align once, apply the kernel, never index"
666
+ * (`np.frompyfunc(f, n, 1)(*np.broadcast_arrays(...))`).
667
+ *
668
+ * **Broadcasting Rules** (Excel's; {@link broadcast} shares them):
669
+ * - A scalar or a 1×1 range FILLS: it is the same value at every position.
670
+ * - A dimension of 1 TILES across the largest extent in that dimension (`tile`, default on).
671
+ * - A shorter dimension PADS: past its extent the operand contributes `misalignValue`
672
+ * (default `#N/A`). The pad is an ARGUMENT, reaching the callback in that operand's slot for
673
+ * the callback to decide what it means; it is not a result written around the callback.
674
+ * - The output is `max(rows)` × `max(cols)` over the operands, anchored at this range's origin.
675
+ *
676
+ * **Iteration is sparse.** The positions visited are the UNION of the operands' stored cells
677
+ * after broadcast mapping; an operand with no cell at a visited position contributes its empty
678
+ * value (`null`, or the empty of `type`). Where EVERY shaped operand is blank the answer depends
679
+ * only on which operands are padded there, so those positions form REGIONS of equal padding,
680
+ * one per combination of operand extents, and `blankRegions` says how they are answered:
681
+ * `'each'` (default) calls the callback per position with its coordinates; `'once'` calls it once
682
+ * per region (the pandas `SparseArray` rule: the kernel runs once on `fill_value` and per stored
683
+ * entry otherwise) — the unpadded region's answer is what the result's empty positions read as,
684
+ * a padded region is written as runs. A region call carries `coords` of
685
+ * `{ rowIndex: -1, colIndex: -1 }`: it answers for many positions and no one of them. Under
686
+ * `'once'` a stretch of stored positions holding one value across every operand is one call too.
687
+ * An operand's own empty positions read as its empty value (a `fillEmpty`, an earlier
688
+ * elementwise result) and are never visited one by one.
689
+ *
690
+ * @param others Additional ranges or scalar values to align with this range, in argument order.
691
+ * @param callbackFn Called at each position with the aligned values (`values[0]` is this
692
+ * range's, `values[i + 1]` is `others[i]`'s), the position relative to the output, and this
693
+ * range. Returns the output value for that position. `values` is ONE buffer reused across
694
+ * calls (the ufunc inner-loop rule): read it, do not retain it.
695
+ * @param options {@link IRange.ZipOptions}. `type` reads EVERY operand's members, and the
696
+ * empty, through that type; it defaults to this range's own type, as {@link broadcast} does.
697
+ * Pass `type: null` to read every operand raw.
698
+ * @returns A new, untyped range of the broadcast shape (the values are the callback's, so this
699
+ * range's type does not describe them).
635
700
  *
636
- * Broadcast semantics follow Excel conventions:
637
- * - A 1×1 scalar or range broadcasts to fill any shape.
638
- * - A 1×N or N×1 range broadcasts to fill the missing dimension.
639
- * - Ranges with incompatible shapes produce a `#VALUE!` error.
640
- *
641
- * @param others Additional ranges or scalar values to zip with this range.
642
- * @param callbackFn Called at each position with the broadcast-resolved values from
643
- * all ranges (`values[0]` is from `this`, `values[1]` onward are from `others`),
644
- * the current cell coordinates, and the original range. Returns the output value
645
- * for that position.
646
- * @param options {@link IRange.IteratorOptions}
647
- * @returns A new range of the same shape as the broadcast result.
701
+ * @example
702
+ * // A 3×1 beside a 1×3 is 3×3: rows tile from the first, columns from the second.
703
+ * const sums = a.zip([b, 10], ([x, y, k]) => (x as number) + (y as number) + (k as number));
648
704
  */
649
- zip(others: (IRange | Scalar)[], callbackFn: (values: Scalar[], coords: IRange.CellCoords, range: IRange<T>) => Scalar, options?: IRange.IteratorOptions): this;
705
+ zip(others: (IRange | Scalar)[], callbackFn: (values: Scalar[], coords: IRange.CellCoords, range: IRange<T>) => Scalar, options?: IRange.ZipOptions): this;
650
706
  /**
651
707
  * Returns a string representation of the range.
652
708
  */
653
709
  toString(): string;
654
- /**
655
- * Returns `true` if the range is a literal range from a parameter.
656
- */
657
- isLiteral(): boolean;
658
- /**
659
- * Returns `true` if the range is a scalar.
660
- */
661
- isScalar(): boolean;
662
- /**
663
- * Returns `true` if the range is a vector (1D) either row or column.
664
- */
665
- isVector(): boolean;
666
710
  /**
667
711
  * Returns `true` if the range is empty.
668
712
  */
@@ -695,6 +739,17 @@ export declare namespace IRange {
695
739
  /**
696
740
  * Options for controlling iteration behavior.
697
741
  */
742
+ /**
743
+ * Options for {@link IRange.indexOf}.
744
+ */
745
+ interface IndexOfOptions {
746
+ /**
747
+ * Search from the last member back, answering the last match.
748
+ *
749
+ * @defaultValue `false`
750
+ */
751
+ reverse?: boolean;
752
+ }
698
753
  interface IteratorOptions extends IRange.ValueOptions {
699
754
  /**
700
755
  * The direction of the iteration.
@@ -848,6 +903,8 @@ export declare namespace IRange {
848
903
  * - It expands the result to the **union of both range sizes**.
849
904
  * - Any cells that do not have valid corresponding values from the inputs
850
905
  * will be filled with `misalignValue` instead of causing an error.
906
+ * - `zip()`, `mask()` and `choose()` always pad and default it to `#N/A`; in `zip()` the pad
907
+ * is delivered to the callback as that operand's value rather than written as the result.
851
908
  *
852
909
  * @example
853
910
  * // Throws an error if A and B are not naturally broadcast-compatible
@@ -869,6 +926,53 @@ export declare namespace IRange {
869
926
  */
870
927
  otherType?: ScalarType;
871
928
  }
929
+ /**
930
+ * Options for {@link IRange.map}.
931
+ */
932
+ interface MapOptions extends IRange.IteratorOptions {
933
+ /**
934
+ * How the positions the range does not store are answered.
935
+ *
936
+ * @defaultValue `'each'`
937
+ *
938
+ * @remarks
939
+ * `'each'` is the sparse walk: an empty position is not visited (or, with `includeEmpty`,
940
+ * visited with its coordinates). `'once'` calls the callback a single time for every empty
941
+ * position, with `coords` of `{ rowIndex: -1, colIndex: -1 }` and the value an empty reads
942
+ * as, and every one of them takes that answer — the {@link IRange.zip} rule. Under `'once'` a
943
+ * stretch of stored positions holding one value is also one call; a callback that reads only
944
+ * its value gets that on its own (it cannot tell), and one that reads `coords` opts in here.
945
+ */
946
+ blankRegions?: 'each' | 'once';
947
+ }
948
+ /**
949
+ * Options for {@link IRange.zip}.
950
+ */
951
+ interface ZipOptions extends IRange.ValuesOptions {
952
+ /**
953
+ * Whether a dimension of 1 tiles across the largest extent in that dimension.
954
+ *
955
+ * @defaultValue `true`
956
+ *
957
+ * @remarks
958
+ * Off, a dimension of 1 is a short dimension like any other and pads `misalignValue` past
959
+ * its one position: Excel's `MAP` rule, where a 1-row array is not stretched.
960
+ */
961
+ tile?: boolean;
962
+ /**
963
+ * How the positions where EVERY shaped operand is blank are answered.
964
+ *
965
+ * @defaultValue `'each'`
966
+ *
967
+ * @remarks
968
+ * Such positions form regions of equal padding (see {@link IRange.zip}). `'each'` visits every
969
+ * one with its coordinates; `'once'` calls the callback a single time per region, with
970
+ * `coords` of `{ rowIndex: -1, colIndex: -1 }`, and writes the region as runs. An explicit
971
+ * option rather than a callback-arity gate, so a callback that reads `coords` for the positions
972
+ * it IS given can still have the blanks answered in one call.
973
+ */
974
+ blankRegions?: 'each' | 'once';
975
+ }
872
976
  /**
873
977
  * Options used during masking.
874
978
  */
@@ -1016,6 +1120,15 @@ export declare namespace IRange {
1016
1120
  rowEnd: number;
1017
1121
  /**
1018
1122
  * An optional identifier for the 2D space these coordinates are relative to.
1123
+ *
1124
+ * @remarks
1125
+ * `scope` was added after the coords reuse/scratch patterns were established, so it is the
1126
+ * field a reused object is most likely to carry STALE. The contract: a function that writes
1127
+ * its result into a caller-supplied reuse object must write `scope` unconditionally —
1128
+ * `null` when the result is unscoped, never left over from the previous occupant
1129
+ * (a leaked scope silently re-targets the coords at another sheet's grid). Writing it
1130
+ * conditionally is only safe on a freshly created object. Geometry-only helpers that
1131
+ * never read or write `scope` (CoordUtils) are exempt; their scope-aware wrappers own it.
1019
1132
  */
1020
1133
  scope?: string | null;
1021
1134
  }
@@ -1033,6 +1146,7 @@ export declare namespace IRange {
1033
1146
  rowIndex: number;
1034
1147
  /**
1035
1148
  * An optional identifier for the 2D space these coordinates are relative to.
1149
+ * See the remark on {@link Coords.scope} for the reuse-object contract.
1036
1150
  */
1037
1151
  scope?: string | null;
1038
1152
  }
@@ -19,6 +19,20 @@ import { FormulaError } from './FormulaError';
19
19
  * 5. Quick introspection without triggering a full range scan
20
20
  */
21
21
  export interface IRangeStats {
22
+ /**
23
+ * Returns the bounding box of the cells these stats cover, in the coordinates of the scanned
24
+ * grid.
25
+ *
26
+ * @returns The smallest rectangle containing every covered coordinate, or `null` when the
27
+ * stats cover nothing.
28
+ *
29
+ * @remarks
30
+ * Stats over several areas (a multi-area range, or a composite over several ranges) return the
31
+ * union box, which can include cells outside every area — and a composite's children may sit
32
+ * on different sheets, so its box is a shape, not an address. `getSize()` is the exact cell
33
+ * count; this is where those cells are.
34
+ */
35
+ getCoords(): IRange.Coords | null;
22
36
  /**
23
37
  * Returns the total number of cells in the range, including empty cells.
24
38
  *
@@ -242,7 +256,9 @@ export declare namespace IRangeStats {
242
256
  /**
243
257
  * Returns the k-th largest numeric value (1-based).
244
258
  *
245
- * @param k The rank of the value to return, where 1 is the largest value. Must be a positive integer.
259
+ * @param k The rank of the value to return, where 1 is the largest value. A fractional rank
260
+ * becomes a 0-based index before it truncates — over five values `k = 2.5` is the
261
+ * THIRD largest, not the second.
246
262
  * @returns `null` if k is out of range.
247
263
  *
248
264
  * @remarks
@@ -252,7 +268,8 @@ export declare namespace IRangeStats {
252
268
  /**
253
269
  * Returns the k-th smallest numeric value (1-based).
254
270
  *
255
- * @param k The rank of the value to return, where 1 is the smallest value. Must be a positive integer.
271
+ * @param k The rank of the value to return, where 1 is the smallest value. A fractional rank
272
+ * becomes a 0-based index before it truncates.
256
273
  * @returns `null` if k is out of range.
257
274
  *
258
275
  * @remarks
@@ -434,6 +451,19 @@ export declare namespace IRangeStats {
434
451
  * This is a convenience method (visitedCount < totalCount)
435
452
  */
436
453
  isTruncated(): boolean;
454
+ /**
455
+ * Returns the bounding box of a value's occurrences, in the coordinates of the scanned grid.
456
+ *
457
+ * @param value The value to look up
458
+ * @returns The smallest rectangle containing every occurrence, or `null` when the value was
459
+ * not found, is blank, or the frequencies were merged from several ranges (no single grid).
460
+ *
461
+ * @remarks
462
+ * Tracked by the same scan that counts, so it costs nothing extra. A box whose cell count
463
+ * equals {@link getFrequency} is a contiguous block — the value lives in exactly that
464
+ * rectangle; a larger box means the occurrences are scattered within it.
465
+ */
466
+ getExtent(value: Scalar): IRange.Coords | null;
437
467
  }
438
468
  /**
439
469
  * Options for finding top values in a range.
@@ -54,9 +54,32 @@ export interface IRuntime {
54
54
  */
55
55
  getOS(): string;
56
56
  /**
57
- * Returns the current timestamp as a serialized number.
57
+ * Returns the clock this runtime reads, as epoch milliseconds.
58
+ *
59
+ * @remarks
60
+ * A workbook's own runtime answers the current commit's timestamp, so every cell of a
61
+ * calculation pass agrees and collaborators on that commit agree with each other. A runtime
62
+ * built by a host answers whatever that host supplied — a pinned clock makes `NOW` and `TODAY`
63
+ * reproducible, and an unpinned one is the wall clock, which advances between reads.
58
64
  */
59
65
  getNow: () => number;
66
+ /**
67
+ * Returns a random number in `[0, 1)` — one per calculation pass, as {@link getNow} is one
68
+ * clock reading per pass.
69
+ *
70
+ * @remarks
71
+ * This is not a cell's value. Each cell's draw is derived from this one, its position and which
72
+ * draw it is within that cell, so cells that do not depend on each other — which the engine is
73
+ * free to evaluate in any order — get the values their positions earn rather than the values
74
+ * their turn earned.
75
+ *
76
+ * Pinning it therefore pins `RAND`, `RANDBETWEEN` and `RANDARRAY`, exactly as pinning
77
+ * {@link getNow} pins `NOW` and `TODAY`. Left alone it is `Math.random`, drawn afresh each pass,
78
+ * so volatiles move on every recalculation.
79
+ *
80
+ * Values reproduce within one release, not across releases.
81
+ */
82
+ getRandom: () => number;
60
83
  /**
61
84
  * Returns the current user or `null` if no user information is provided.
62
85
  */
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Declares a parameter the engine may hand over UNEVALUATED — the argument's subtree is not run
3
+ * before the call, and it runs only if this call's answer needs it.
4
+ *
5
+ * @remarks
6
+ * An argument that is a bare reference has always been cheap to ignore: it resolves into a
7
+ * register and a body that never reads it never dereferences it. An argument that is an
8
+ * EXPRESSION is not — `IF(FALSE,B1+1,1)` evaluates `B1+1` before `IF` runs, so the untaken branch
9
+ * opens `B1` anyway. That is a correctness difference and not a cost one: a read is what decides
10
+ * a circular reference and what registers a volatile, so an arm nobody takes can report a cycle
11
+ * the sheet does not have.
12
+ *
13
+ * The marker is what closes it, and it is a CONTRACT rather than an inference. Nothing in
14
+ * JavaScript intercepts a plain parameter read, so a body holding a deferred value is only safe
15
+ * while it never touches it — which the declaration has to say out loud:
16
+ *
17
+ * ```ts
18
+ * export function IF(
19
+ * logicalTest: boolean,
20
+ * valueIfTrue: Lazy<AcceptError<any>>,
21
+ * valueIfFalse: Lazy<AcceptError<any>, false>
22
+ * ): any {
23
+ * return logicalTest ? valueIfTrue : valueIfFalse;
24
+ * }
25
+ * ```
26
+ *
27
+ * The parameter is ALWAYS an {@link ILazy} handle and never a union with the bare value, so the
28
+ * body has two things it may do with it and the type says which is which:
29
+ *
30
+ * - **return it unread**, which is what every branch-selecting function does. The engine
31
+ * recognizes its own handle coming back as the call's result and evaluates that subtree then.
32
+ * - **read it** with {@link ILazy.getValue}, which forces the subtree in place. Synchronous only:
33
+ * the engine is mid-formula there and has no way to wait, so an ASYNC subtree at a forced
34
+ * handle is an error rather than a wrong answer.
35
+ *
36
+ * Only the RESULT positions are marked. A condition, an index and a case value are eager, which
37
+ * is what keeps `AND(FALSE,A1)` a circular reference: laziness is a property of what the function
38
+ * READS, not of the syntax around it.
39
+ *
40
+ * The engine may DECLINE to defer at any time and hand the body a handle that is already full —
41
+ * an absent argument, an async subtree, a lift that needs every arm anyway. `getValue()` then
42
+ * returns the value it was built with. The author cannot tell the two apart and must not try.
43
+ *
44
+ * @typeParam T - what the argument evaluates to; the marker is otherwise transparent.
45
+ * @typeParam V - what an ABSENT argument means, as a literal. It replaces the `= false`
46
+ * initializer an ordinary parameter would carry, which cannot typecheck against a handle;
47
+ * declaring it makes the parameter optional and the engine supplies the value, so the body
48
+ * still sees nothing but a handle.
49
+ *
50
+ * @see {@link IFunction.ParameterTypeJSON.lazy}
51
+ */
52
+ export type Lazy<T, V = never> = ILazy<T | V>;
53
+ /**
54
+ * The handle a {@link Lazy} parameter receives: an argument's subtree, anchored at the calling
55
+ * cell, that has either not been evaluated yet or has been evaluated already.
56
+ *
57
+ * @remarks
58
+ * The brand exists for the same reason {@link Lambda}'s does — the value-layer sites (coercion,
59
+ * the operators, a range's typed read, a cell's final value) RECOGNIZE a handle and refuse it, so
60
+ * that one can never reach a grid, a spill or a named-formula result. A handle is only ever a
61
+ * live call's argument.
62
+ */
63
+ export interface ILazy<T = any> {
64
+ /** The brand itself — the property every recognizing site reads. */
65
+ readonly isLazy: true;
66
+ /**
67
+ * Evaluates the argument and answers its value, forcing the subtree where the engine deferred
68
+ * it and answering the value it already has where it declined to.
69
+ *
70
+ * Forced at most once: a second call answers the same value.
71
+ */
72
+ getValue(): T;
73
+ }
74
+ /** `true` for a deferred-argument handle — see {@link ILazy}. */
75
+ export declare const isLazyValue: (value: unknown) => value is ILazy;
76
+ //# sourceMappingURL=Lazy.d.ts.map
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Declares that the members reaching a REPEATING scalar parameter are COERCED rather than
3
+ * filtered — one that cannot be coerced is `#VALUE!` for the whole call.
4
+ *
5
+ * The native functions FILTER: `SUM({1,"8",TRUE})` is 1, the text and the boolean skipped, and a
6
+ * blank cell contributes nothing. Excel's Analysis-ToolPak heritage coerces at the slot instead,
7
+ * and the difference is visible in both directions:
8
+ *
9
+ * | argument | native (`SUM`) | this marker (`GCD`) |
10
+ * | --- | --- | --- |
11
+ * | numeric text, `{6,"8",12}` | skipped | parses — `GCD({6,"8",12})` is 2 |
12
+ * | a boolean, `{6,TRUE,12}` | skipped | `#VALUE!` for the whole call |
13
+ * | a blank inside a multi-cell range | skipped | reads 0 — `LCM(6,A1:A2)` is 0 |
14
+ *
15
+ * A DIRECT argument follows the same rule (`GCD(6,TRUE,12)` is `#VALUE!` where `SUM(6,TRUE,12)`
16
+ * is 19). A one-cell blank reference and an omitted argument are still skipped either way: they
17
+ * never became members.
18
+ *
19
+ * Only a repeating slot has members to filter, so the marker means nothing on a plain scalar,
20
+ * range or lambda parameter and the linter refuses it there. It is the repeating counterpart of
21
+ * {@link NoLift}, which is the same heritage's reading of a NON-repeating scalar slot.
22
+ *
23
+ * It may be written over the group or on the slot — `NoFilter<number[]>` and `NoFilter<number>[]`
24
+ * mean the same thing, as they do for `AcceptError`.
25
+ *
26
+ * At the type level it is `T`.
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * export function GCD(number: NoFilter<number>[]): number { ... }
31
+ * ```
32
+ */
33
+ export type NoFilter<T> = T;
34
+ //# sourceMappingURL=NoFilter.d.ts.map
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Declares that a SCALAR parameter refuses a multi-cell REFERENCE with `#VALUE!` instead of
3
+ * broadcasting over it.
4
+ *
5
+ * The native functions lift: `ROUND(B1:B3,3)` spills three results. Excel's Analysis-ToolPak
6
+ * heritage refuses at the slot instead — `EDATE(A1:A3,1)` is `#VALUE!` — and the refusal is per
7
+ * PARAMETER: `NETWORKDAYS.INTL` refuses a reference at its two date slots and lifts one at its
8
+ * `weekend` slot. Three qualifications:
9
+ *
10
+ * | argument at the slot | answer |
11
+ * | --- | --- |
12
+ * | a multi-cell REFERENCE, `A1:A3` | `#VALUE!` — the call never runs |
13
+ * | an ARRAY, `{3,4,5}` | lifts as usual (`MROUND({3,4,5},5)` spills) |
14
+ * | a one-cell reference or a value | an ordinary argument |
15
+ *
16
+ * Only a scalar slot can lift, so the marker means nothing on a range, list or lambda parameter
17
+ * and the linter refuses it there. A REPEATING scalar slot is also never this marker's business —
18
+ * it absorbs a container as members whatever the function's heritage (`GCD(C1:C3)` is 4); its
19
+ * ATP reading is `NoFilter<T>`.
20
+ *
21
+ * At the type level it is `T`.
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * export function NETWORKDAYS_INTL(
26
+ * startDate: NoLift<Scalar>, endDate: NoLift<Scalar>, weekend?: Scalar, holidays?: Flatten<Scalar>
27
+ * ): number { ... }
28
+ * ```
29
+ */
30
+ export type NoLift<T> = T;
31
+ //# sourceMappingURL=NoLift.d.ts.map