@devmm/puredocs-excel-formula 1.0.7 → 1.0.8

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/dist/index.d.cts CHANGED
@@ -152,8 +152,11 @@ declare const enum TokenType {
152
152
  SpillReference = 24,
153
153
  Exclamation = 25,
154
154
  AtSign = 26,
155
- ErrorLiteral = 27,
156
- EOF = 28
155
+ LeftBrace = 27,
156
+ RightBrace = 28,
157
+ Semicolon = 29,
158
+ ErrorLiteral = 30,
159
+ EOF = 31
157
160
  }
158
161
  interface Token {
159
162
  readonly type: TokenType;
@@ -170,7 +173,8 @@ interface Token {
170
173
  * - Error literals (#N/A, #REF!, #VALUE!, etc.)
171
174
  * - Quoted sheet references ('My Sheet'!A1)
172
175
  * - Unquoted sheet references (Sheet1!A1)
173
- * - _xlfn. prefix stripping
176
+ * - _xlfn. / _xlws. / _xlpm. prefix stripping
177
+ * - Array constants ({1,2;3,4})
174
178
  * - Cell references ($A$1, A1, AA123)
175
179
  * - Named ranges
176
180
  * - @ implicit intersection operator
package/dist/index.d.ts CHANGED
@@ -152,8 +152,11 @@ declare const enum TokenType {
152
152
  SpillReference = 24,
153
153
  Exclamation = 25,
154
154
  AtSign = 26,
155
- ErrorLiteral = 27,
156
- EOF = 28
155
+ LeftBrace = 27,
156
+ RightBrace = 28,
157
+ Semicolon = 29,
158
+ ErrorLiteral = 30,
159
+ EOF = 31
157
160
  }
158
161
  interface Token {
159
162
  readonly type: TokenType;
@@ -170,7 +173,8 @@ interface Token {
170
173
  * - Error literals (#N/A, #REF!, #VALUE!, etc.)
171
174
  * - Quoted sheet references ('My Sheet'!A1)
172
175
  * - Unquoted sheet references (Sheet1!A1)
173
- * - _xlfn. prefix stripping
176
+ * - _xlfn. / _xlws. / _xlpm. prefix stripping
177
+ * - Array constants ({1,2;3,4})
174
178
  * - Cell references ($A$1, A1, AA123)
175
179
  * - Named ranges
176
180
  * - @ implicit intersection operator
package/dist/index.js CHANGED
@@ -446,8 +446,11 @@ var TokenType = /* @__PURE__ */ ((TokenType2) => {
446
446
  TokenType2[TokenType2["SpillReference"] = 24] = "SpillReference";
447
447
  TokenType2[TokenType2["Exclamation"] = 25] = "Exclamation";
448
448
  TokenType2[TokenType2["AtSign"] = 26] = "AtSign";
449
- TokenType2[TokenType2["ErrorLiteral"] = 27] = "ErrorLiteral";
450
- TokenType2[TokenType2["EOF"] = 28] = "EOF";
449
+ TokenType2[TokenType2["LeftBrace"] = 27] = "LeftBrace";
450
+ TokenType2[TokenType2["RightBrace"] = 28] = "RightBrace";
451
+ TokenType2[TokenType2["Semicolon"] = 29] = "Semicolon";
452
+ TokenType2[TokenType2["ErrorLiteral"] = 30] = "ErrorLiteral";
453
+ TokenType2[TokenType2["EOF"] = 31] = "EOF";
451
454
  return TokenType2;
452
455
  })(TokenType || {});
453
456
  function makeToken(type, value2, position) {
@@ -461,6 +464,8 @@ var FormulaException = class extends Error {
461
464
  this.name = "FormulaException";
462
465
  }
463
466
  };
467
+ var UNDERSCORE = 95;
468
+ var RESERVED_PREFIXES = ["_XLFN.", "_XLWS.", "_XLPM."];
464
469
  var KNOWN_ERRORS = /* @__PURE__ */ new Set([
465
470
  "#NULL!",
466
471
  "#DIV/0!",
@@ -472,7 +477,7 @@ var KNOWN_ERRORS = /* @__PURE__ */ new Set([
472
477
  "#CALC!",
473
478
  "#SPILL!"
474
479
  ]);
475
- var _formula, _pos, _FormulaLexer_instances, readNextToken_fn, readString_fn, readErrorLiteral_fn, readQuotedSheetRef_fn, readNumber_fn, readIdentifier_fn, readCellRefChars_fn, readOperator_fn, skipWhitespace_fn;
480
+ var _formula, _pos, _FormulaLexer_instances, readNextToken_fn, readString_fn, readErrorLiteral_fn, readQuotedSheetRef_fn, readNumber_fn, readIdentifier_fn, stripReservedPrefixes_fn, readCellRefChars_fn, readOperator_fn, skipWhitespace_fn;
476
481
  var FormulaLexer = class {
477
482
  constructor(formula) {
478
483
  __privateAdd(this, _FormulaLexer_instances);
@@ -490,7 +495,7 @@ var FormulaLexer = class {
490
495
  const token = __privateMethod(this, _FormulaLexer_instances, readNextToken_fn).call(this);
491
496
  if (token) tokens.push(token);
492
497
  }
493
- tokens.push(makeToken(28 /* EOF */, "", __privateGet(this, _pos)));
498
+ tokens.push(makeToken(31 /* EOF */, "", __privateGet(this, _pos)));
494
499
  return tokens;
495
500
  }
496
501
  };
@@ -544,7 +549,7 @@ readErrorLiteral_fn = function() {
544
549
  if (!KNOWN_ERRORS.has(upper2)) {
545
550
  throw new FormulaException(`Unknown error literal "${raw}" at position ${start}.`);
546
551
  }
547
- return makeToken(27 /* ErrorLiteral */, upper2, start);
552
+ return makeToken(30 /* ErrorLiteral */, upper2, start);
548
553
  };
549
554
  readQuotedSheetRef_fn = function() {
550
555
  const start = __privateWrapper(this, _pos)._++;
@@ -607,10 +612,8 @@ readIdentifier_fn = function() {
607
612
  } else break;
608
613
  }
609
614
  let upper2 = raw.toUpperCase();
610
- if (upper2.startsWith("_XLFN.")) {
611
- raw = raw.slice(6);
612
- upper2 = upper2.slice(6);
613
- }
615
+ raw = __privateMethod(this, _FormulaLexer_instances, stripReservedPrefixes_fn).call(this, raw, upper2);
616
+ if (raw.length !== upper2.length) upper2 = upper2.slice(upper2.length - raw.length);
614
617
  if (upper2 === "TRUE") return makeToken(2 /* Boolean */, "TRUE", start);
615
618
  if (upper2 === "FALSE") return makeToken(2 /* Boolean */, "FALSE", start);
616
619
  if (__privateGet(this, _formula)[__privateGet(this, _pos)] === "!") {
@@ -632,6 +635,30 @@ readIdentifier_fn = function() {
632
635
  }
633
636
  return makeToken(23 /* NamedRange */, raw, start);
634
637
  };
638
+ /**
639
+ * Drops Excel's storage-level name decorations. They carry no semantics — the
640
+ * writer stamps them so an older reader fails loudly instead of silently
641
+ * mis-evaluating — and they *stack*: `_xlfn._xlws.FILTER` wears two. Stripping
642
+ * one prefix once, as this used to, leaves `_XLWS.FILTER` unresolvable.
643
+ *
644
+ * Every identifier in every formula passes through here, so the common case is
645
+ * gated on a single char-code test: a name not starting with '_' returns
646
+ * untouched without so much as a comparison against the prefix table.
647
+ */
648
+ stripReservedPrefixes_fn = function(raw, upper2) {
649
+ if (raw.charCodeAt(0) !== UNDERSCORE) return raw;
650
+ let cut = 0;
651
+ outer: for (; ; ) {
652
+ for (const prefix of RESERVED_PREFIXES) {
653
+ if (upper2.startsWith(prefix, cut)) {
654
+ cut += prefix.length;
655
+ continue outer;
656
+ }
657
+ }
658
+ break;
659
+ }
660
+ return cut > 0 && cut < raw.length ? raw.slice(cut) : raw;
661
+ };
635
662
  readCellRefChars_fn = function() {
636
663
  let ref = "";
637
664
  while (__privateGet(this, _pos) < __privateGet(this, _formula).length) {
@@ -675,6 +702,14 @@ readOperator_fn = function() {
675
702
  return makeToken(26 /* AtSign */, "@", start);
676
703
  case "!":
677
704
  return makeToken(25 /* Exclamation */, "!", start);
705
+ case "{":
706
+ return makeToken(27 /* LeftBrace */, "{", start);
707
+ case "}":
708
+ return makeToken(28 /* RightBrace */, "}", start);
709
+ // Inside an array constant ';' separates rows. Excel always stores ',' as
710
+ // the argument separator regardless of locale, so ';' has no other role.
711
+ case ";":
712
+ return makeToken(29 /* Semicolon */, ";", start);
678
713
  case "<":
679
714
  if (__privateGet(this, _formula)[__privateGet(this, _pos)] === ">") {
680
715
  __privateWrapper(this, _pos)._++;
@@ -757,6 +792,19 @@ var ErrorNode = class extends FormulaNode {
757
792
  return this.errorValue;
758
793
  }
759
794
  };
795
+ var _value;
796
+ var ArrayLiteralNode = class extends FormulaNode {
797
+ constructor(array) {
798
+ super();
799
+ this.array = array;
800
+ __privateAdd(this, _value);
801
+ __privateSet(this, _value, FormulaValue.array(array));
802
+ }
803
+ evaluate() {
804
+ return __privateGet(this, _value);
805
+ }
806
+ };
807
+ _value = new WeakMap();
760
808
  var BlankNode = class extends FormulaNode {
761
809
  evaluate() {
762
810
  return FormulaValue.blank;
@@ -1034,7 +1082,7 @@ var CallNode = class extends FormulaNode {
1034
1082
  };
1035
1083
 
1036
1084
  // src/formula-parser.ts
1037
- var _tokens, _pos2, _FormulaParser_instances, current_get, advance_fn, expect_fn, parseComparison_fn, parseConcatenation_fn, parseAddSub_fn, parseMulDiv_fn, parseUnary_fn, parsePower_fn, parsePercent_fn, parsePostfix_fn, parsePrimary_fn, tryParseFullRange_fn, parseFunction_fn, parseArgList_fn;
1085
+ var _tokens, _pos2, _FormulaParser_instances, current_get, advance_fn, expect_fn, parseComparison_fn, parseConcatenation_fn, parseAddSub_fn, parseMulDiv_fn, parseUnary_fn, parsePower_fn, parsePercent_fn, parsePostfix_fn, parsePrimary_fn, tryParseFullRange_fn, parseArrayLiteral_fn, parseArrayElement_fn, parseFunction_fn, parseArgList_fn;
1038
1086
  var FormulaParser = class {
1039
1087
  constructor(tokens) {
1040
1088
  __privateAdd(this, _FormulaParser_instances);
@@ -1045,7 +1093,7 @@ var FormulaParser = class {
1045
1093
  parse() {
1046
1094
  __privateSet(this, _pos2, 0);
1047
1095
  const node = __privateMethod(this, _FormulaParser_instances, parseComparison_fn).call(this);
1048
- if (__privateGet(this, _FormulaParser_instances, current_get).type !== 28 /* EOF */) {
1096
+ if (__privateGet(this, _FormulaParser_instances, current_get).type !== 31 /* EOF */) {
1049
1097
  throw new FormulaException(
1050
1098
  `Unexpected token '${__privateGet(this, _FormulaParser_instances, current_get).value}' at position ${__privateGet(this, _FormulaParser_instances, current_get).position}.`
1051
1099
  );
@@ -1057,7 +1105,7 @@ _tokens = new WeakMap();
1057
1105
  _pos2 = new WeakMap();
1058
1106
  _FormulaParser_instances = new WeakSet();
1059
1107
  current_get = function() {
1060
- return __privateGet(this, _tokens)[__privateGet(this, _pos2)] ?? { type: 28 /* EOF */, value: "", position: -1 };
1108
+ return __privateGet(this, _tokens)[__privateGet(this, _pos2)] ?? { type: 31 /* EOF */, value: "", position: -1 };
1061
1109
  };
1062
1110
  advance_fn = function() {
1063
1111
  const t = __privateGet(this, _FormulaParser_instances, current_get);
@@ -1172,7 +1220,7 @@ parsePrimary_fn = function() {
1172
1220
  case 2 /* Boolean */:
1173
1221
  __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1174
1222
  return new BooleanNode(token.value === "TRUE");
1175
- case 27 /* ErrorLiteral */:
1223
+ case 30 /* ErrorLiteral */:
1176
1224
  __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1177
1225
  return new ErrorNode(FormulaValue.error(FormulaValue.errorFromString(token.value)));
1178
1226
  case 3 /* CellReference */: {
@@ -1211,6 +1259,8 @@ parsePrimary_fn = function() {
1211
1259
  return new SpillReferenceNode(token.value);
1212
1260
  case 5 /* Function */:
1213
1261
  return __privateMethod(this, _FormulaParser_instances, parseFunction_fn).call(this);
1262
+ case 27 /* LeftBrace */:
1263
+ return __privateMethod(this, _FormulaParser_instances, parseArrayLiteral_fn).call(this);
1214
1264
  case 6 /* LeftParen */: {
1215
1265
  __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1216
1266
  const expr = __privateMethod(this, _FormulaParser_instances, parseComparison_fn).call(this);
@@ -1245,6 +1295,79 @@ tryParseFullRange_fn = function(startText, sheetName) {
1245
1295
  const hi = Math.max(start.index, end.index);
1246
1296
  return start.kind === "col" ? new FullRangeReferenceNode(lo, hi, null, null, sheetName) : new FullRangeReferenceNode(null, null, lo, hi, sheetName);
1247
1297
  };
1298
+ /**
1299
+ * Parses an array constant: `{1,2;3,4}` — ',' separates columns, ';' rows.
1300
+ *
1301
+ * Excel permits only literals inside one (no references, no calls, not even
1302
+ * arithmetic beyond a leading sign), so the value is fully determined here and
1303
+ * is materialised once into the node instead of being rebuilt on every eval.
1304
+ *
1305
+ * A ragged constant is rejected rather than padded: Excel refuses to accept
1306
+ * one at entry, so a file cannot legitimately contain it, and silently filling
1307
+ * the gaps would invent data the author never wrote.
1308
+ */
1309
+ parseArrayLiteral_fn = function() {
1310
+ const open = __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1311
+ const rows2 = [];
1312
+ let row2 = [];
1313
+ for (; ; ) {
1314
+ row2.push(__privateMethod(this, _FormulaParser_instances, parseArrayElement_fn).call(this));
1315
+ if (__privateGet(this, _FormulaParser_instances, current_get).type === 8 /* Comma */) {
1316
+ __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1317
+ continue;
1318
+ }
1319
+ if (__privateGet(this, _FormulaParser_instances, current_get).type === 29 /* Semicolon */) {
1320
+ __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1321
+ rows2.push(row2);
1322
+ row2 = [];
1323
+ continue;
1324
+ }
1325
+ break;
1326
+ }
1327
+ rows2.push(row2);
1328
+ __privateMethod(this, _FormulaParser_instances, expect_fn).call(this, 28 /* RightBrace */);
1329
+ const cols = rows2[0].length;
1330
+ if (rows2.some((r) => r.length !== cols)) {
1331
+ throw new FormulaException(
1332
+ `Array constant at position ${open.position} has rows of differing lengths.`
1333
+ );
1334
+ }
1335
+ const array = new ArrayValue(rows2.length, cols);
1336
+ for (let r = 0; r < rows2.length; r++) {
1337
+ for (let c = 0; c < cols; c++) array.set(r, c, rows2[r][c]);
1338
+ }
1339
+ return new ArrayLiteralNode(array);
1340
+ };
1341
+ /** One element of an array constant: a literal, optionally signed. */
1342
+ parseArrayElement_fn = function() {
1343
+ let negate = false;
1344
+ let signed = false;
1345
+ while (__privateGet(this, _FormulaParser_instances, current_get).type === 10 /* Minus */ || __privateGet(this, _FormulaParser_instances, current_get).type === 9 /* Plus */) {
1346
+ if (__privateGet(this, _FormulaParser_instances, current_get).type === 10 /* Minus */) negate = !negate;
1347
+ signed = true;
1348
+ __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1349
+ }
1350
+ const token = __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1351
+ switch (token.type) {
1352
+ case 0 /* Number */: {
1353
+ const n = parseFloat(token.value);
1354
+ if (isNaN(n)) throw new FormulaException(`Invalid number: ${token.value}`);
1355
+ return FormulaValue.number(negate ? -n : n);
1356
+ }
1357
+ case 1 /* String */:
1358
+ if (signed) break;
1359
+ return FormulaValue.text(token.value);
1360
+ case 2 /* Boolean */:
1361
+ if (signed) break;
1362
+ return FormulaValue.boolean(token.value === "TRUE");
1363
+ case 30 /* ErrorLiteral */:
1364
+ if (signed) break;
1365
+ return FormulaValue.error(FormulaValue.errorFromString(token.value));
1366
+ }
1367
+ throw new FormulaException(
1368
+ `Array constants accept only literal values; got '${token.value}' at position ${token.position}.`
1369
+ );
1370
+ };
1248
1371
  parseFunction_fn = function() {
1249
1372
  const nameToken = __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1250
1373
  __privateMethod(this, _FormulaParser_instances, expect_fn).call(this, 6 /* LeftParen */);
@@ -4061,22 +4184,46 @@ function unique(a, c) {
4061
4184
  const out = fromRows(result);
4062
4185
  return byCol2 ? FormulaValue.array(out.arrayVal.transpose()) : out;
4063
4186
  }
4187
+ var numVector = (a, c, i, dflt) => {
4188
+ if (i >= a.length) return { ok: true, v: [dflt] };
4189
+ const val = a[i].evaluate(c);
4190
+ if (val.isError) return { ok: false, e: val };
4191
+ const out = [];
4192
+ for (const item of val.isArray ? val.arrayVal.values() : [val]) {
4193
+ if (item.isBlank) continue;
4194
+ const d = item.tryAsDouble();
4195
+ if (!d.ok) return { ok: false, e: FormulaValue.errorValue };
4196
+ out.push(d.value);
4197
+ }
4198
+ return out.length > 0 ? { ok: true, v: out } : { ok: true, v: [dflt] };
4199
+ };
4064
4200
  function sort(a, c) {
4065
4201
  const v = a[0].evaluate(c);
4066
4202
  if (v.isError) return v;
4067
- const sortIndex = num4(a, c, 1, 1);
4068
- if (!sortIndex.ok) return sortIndex.e;
4069
- const sortOrder = num4(a, c, 2, 1);
4070
- if (!sortOrder.ok) return sortOrder.e;
4203
+ const indices = numVector(a, c, 1, 1);
4204
+ if (!indices.ok) return indices.e;
4205
+ const orders = numVector(a, c, 2, 1);
4206
+ if (!orders.ok) return orders.e;
4071
4207
  const byCol2 = a.length > 3 ? a[3].evaluate(c).coerceToBool().booleanValue : false;
4208
+ if (orders.v.length !== 1 && orders.v.length !== indices.v.length) {
4209
+ return FormulaValue.errorValue;
4210
+ }
4072
4211
  const base = byCol2 ? asArray2(v).transpose() : asArray2(v);
4073
4212
  const rows2 = toRows(base);
4074
- const idx = Math.trunc(sortIndex.n) - 1;
4075
- const dir = sortOrder.n < 0 ? -1 : 1;
4076
- if (rows2.length > 0 && (idx < 0 || idx >= rows2[0].length)) return FormulaValue.errorValue;
4213
+ const keys = indices.v.map((n, i) => ({
4214
+ col: Math.trunc(n) - 1,
4215
+ dir: (orders.v.length === 1 ? orders.v[0] : orders.v[i]) < 0 ? -1 : 1
4216
+ }));
4217
+ const width = rows2.length > 0 ? rows2[0].length : 0;
4218
+ if (rows2.length > 0 && keys.some((k) => k.col < 0 || k.col >= width)) {
4219
+ return FormulaValue.errorValue;
4220
+ }
4077
4221
  const sorted = rows2.map((r, i) => ({ r, i })).sort((x, y) => {
4078
- const cmp = FormulaValue.compare(x.r[idx], y.r[idx]) * dir;
4079
- return cmp !== 0 ? cmp : x.i - y.i;
4222
+ for (const k of keys) {
4223
+ const cmp = FormulaValue.compare(x.r[k.col], y.r[k.col]) * k.dir;
4224
+ if (cmp !== 0) return cmp;
4225
+ }
4226
+ return x.i - y.i;
4080
4227
  }).map((o) => o.r);
4081
4228
  const out = fromRows(sorted);
4082
4229
  return byCol2 ? FormulaValue.array(out.arrayVal.transpose()) : out;