numbl 0.4.12 → 0.4.14

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.
Files changed (37) hide show
  1. package/README.md +0 -4
  2. package/dist-browser/browser/index.d.ts +13 -4
  3. package/dist-browser/browser/protocol.d.ts +25 -1
  4. package/dist-browser/browser/session.d.ts +20 -10
  5. package/dist-browser/browser.js +26 -4
  6. package/dist-browser/numbl-core/fileIOAdapter.d.ts +4 -0
  7. package/dist-browser/numbl-core/native/lapack-bridge.d.ts +1 -1
  8. package/dist-browser/numbl-core/native/wasm-lapack-bridge.d.ts +114 -0
  9. package/dist-browser/numbl-core/native/wasm-lapack-browser.d.ts +34 -0
  10. package/dist-browser/numbl-core/runtime/datetime-format.d.ts +10 -0
  11. package/dist-browser/numbl-core/runtime/display.d.ts +5 -1
  12. package/dist-browser/numbl-core/runtime/index.d.ts +1 -1
  13. package/dist-browser/numbl-core/version.d.ts +1 -1
  14. package/dist-browser/vfs/BrowserFileIOAdapter.d.ts +1 -0
  15. package/dist-browser/workerErrorFormat.d.ts +7 -0
  16. package/dist-cli/cli.js +463 -135
  17. package/dist-graphics/graphics-lib.d.ts +1 -0
  18. package/dist-graphics/index.js +33 -0
  19. package/dist-lib/cli-fileio.d.ts +62 -0
  20. package/dist-lib/cli-scan.d.ts +6 -0
  21. package/dist-lib/cli-system.d.ts +13 -0
  22. package/dist-lib/lib.js +386 -73
  23. package/dist-lib/node.d.ts +30 -0
  24. package/dist-lib/node.js +829 -0
  25. package/dist-lib/numbl-core/fileIOAdapter.d.ts +4 -0
  26. package/dist-lib/numbl-core/native/lapack-bridge.d.ts +1 -1
  27. package/dist-lib/numbl-core/native/wasm-lapack-bridge.d.ts +114 -0
  28. package/dist-lib/numbl-core/native/wasm-lapack-browser.d.ts +34 -0
  29. package/dist-lib/numbl-core/runtime/datetime-format.d.ts +10 -0
  30. package/dist-lib/numbl-core/runtime/display.d.ts +5 -1
  31. package/dist-lib/numbl-core/runtime/index.d.ts +1 -1
  32. package/dist-lib/numbl-core/version.d.ts +1 -1
  33. package/dist-lib/vfs/BrowserFileIOAdapter.d.ts +1 -0
  34. package/dist-site-viewer/assets/{index-CGE1_vZ-.js → index-CqtJ-i-7.js} +322 -322
  35. package/dist-site-viewer/assets/{numbl-worker-DddtCPW_.js → numbl-worker-B-MPuV6f.js} +133 -103
  36. package/dist-site-viewer/index.html +1 -1
  37. package/package.json +6 -2
package/dist-lib/lib.js CHANGED
@@ -819,6 +819,114 @@ var valuesAreEqual = (a, b) => {
819
819
  }
820
820
  };
821
821
 
822
+ // src/numbl-core/runtime/datetime-format.ts
823
+ var MONTH_ABBR = [
824
+ "Jan",
825
+ "Feb",
826
+ "Mar",
827
+ "Apr",
828
+ "May",
829
+ "Jun",
830
+ "Jul",
831
+ "Aug",
832
+ "Sep",
833
+ "Oct",
834
+ "Nov",
835
+ "Dec"
836
+ ];
837
+ var MONTH_FULL = [
838
+ "January",
839
+ "February",
840
+ "March",
841
+ "April",
842
+ "May",
843
+ "June",
844
+ "July",
845
+ "August",
846
+ "September",
847
+ "October",
848
+ "November",
849
+ "December"
850
+ ];
851
+ function pad(n, width) {
852
+ return String(n).padStart(width, "0");
853
+ }
854
+ function formatDatetimePattern(pattern, year, month, day, hour, minute, second) {
855
+ const wholeSec = Math.floor(second);
856
+ const frac = second - wholeSec;
857
+ const monthIdx = Math.max(0, Math.min(11, Math.floor(month) - 1));
858
+ const formatToken = (c, n) => {
859
+ switch (c) {
860
+ case "y":
861
+ case "u":
862
+ if (n === 2) return pad(Math.floor(year) % 100, 2);
863
+ return pad(Math.floor(year), n);
864
+ case "M":
865
+ if (n === 1) return String(Math.floor(month));
866
+ if (n === 2) return pad(Math.floor(month), 2);
867
+ if (n === 3) return MONTH_ABBR[monthIdx];
868
+ return MONTH_FULL[monthIdx];
869
+ case "d":
870
+ return pad(Math.floor(day), n);
871
+ case "H":
872
+ return pad(Math.floor(hour), n);
873
+ case "h":
874
+ return pad(Math.floor(hour) % 12 || 12, n);
875
+ case "m":
876
+ return pad(Math.floor(minute), n);
877
+ case "s":
878
+ return pad(wholeSec, n);
879
+ case "S":
880
+ return pad(Math.floor(frac * Math.pow(10, n)), n);
881
+ case "a":
882
+ return hour < 12 ? "AM" : "PM";
883
+ default:
884
+ throw new RuntimeError(
885
+ `datetime: unsupported format token '${c.repeat(n)}' in '${pattern}'`
886
+ );
887
+ }
888
+ };
889
+ let out = "";
890
+ let i = 0;
891
+ while (i < pattern.length) {
892
+ const c = pattern[i];
893
+ if (c === "'") {
894
+ i++;
895
+ let lit = "";
896
+ let closed = false;
897
+ while (i < pattern.length) {
898
+ if (pattern[i] === "'") {
899
+ if (pattern[i + 1] === "'") {
900
+ lit += "'";
901
+ i += 2;
902
+ continue;
903
+ }
904
+ i++;
905
+ closed = true;
906
+ break;
907
+ }
908
+ lit += pattern[i++];
909
+ }
910
+ if (!closed && lit !== "")
911
+ throw new RuntimeError(
912
+ `datetime: unterminated quoted literal in format '${pattern}'`
913
+ );
914
+ out += lit === "" ? "'" : lit;
915
+ continue;
916
+ }
917
+ if (!/[a-zA-Z]/.test(c)) {
918
+ out += c;
919
+ i++;
920
+ continue;
921
+ }
922
+ let j = i;
923
+ while (j < pattern.length && pattern[j] === c) j++;
924
+ out += formatToken(c, j - i);
925
+ i = j;
926
+ }
927
+ return out;
928
+ }
929
+
822
930
  // src/numbl-core/runtime/display.ts
823
931
  function displayValue(v) {
824
932
  if (isRuntimeNumber(v)) {
@@ -1093,6 +1201,13 @@ function formatDatetimeOrDuration(v) {
1093
1201
  if (v.className === "duration") return formatDurationInstance(v);
1094
1202
  return null;
1095
1203
  }
1204
+ function textField(v, name) {
1205
+ const fv = v.fields.get(name);
1206
+ if (fv === void 0) return null;
1207
+ if (isRuntimeString(fv)) return fv;
1208
+ if (isRuntimeChar(fv)) return fv.value;
1209
+ return null;
1210
+ }
1096
1211
  function formatDatetimeInstance(v) {
1097
1212
  const year = numField(v, "Year");
1098
1213
  const month = numField(v, "Month");
@@ -1102,6 +1217,10 @@ function formatDatetimeInstance(v) {
1102
1217
  const second = numField(v, "Second");
1103
1218
  if (year === null || month === null || day === null || hour === null || minute === null || second === null)
1104
1219
  return null;
1220
+ const fmt = textField(v, "Format");
1221
+ if (fmt !== null && fmt !== "") {
1222
+ return formatDatetimePattern(fmt, year, month, day, hour, minute, second);
1223
+ }
1105
1224
  const mIdx = Math.max(0, Math.min(11, Math.floor(month) - 1));
1106
1225
  const mon = DATETIME_MONTH_ABBR[mIdx];
1107
1226
  const dd = String(Math.floor(day)).padStart(2, "0");
@@ -22321,8 +22440,8 @@ function sprintfFormat(fmt, args) {
22321
22440
  if (width > 0) {
22322
22441
  const padChar = zeroPad ? "0" : " ";
22323
22442
  const padLen = Math.max(0, width - sign.length - s.length);
22324
- const pad = padChar.repeat(padLen);
22325
- result += leftAlign ? sign + s + " ".repeat(padLen) : zeroPad ? sign + pad + s : pad + sign + s;
22443
+ const pad2 = padChar.repeat(padLen);
22444
+ result += leftAlign ? sign + s + " ".repeat(padLen) : zeroPad ? sign + pad2 + s : pad2 + sign + s;
22326
22445
  } else {
22327
22446
  result += sign + s;
22328
22447
  }
@@ -25046,6 +25165,17 @@ function indexIntoRTValue(base, indices) {
25046
25165
  return base.elements[i];
25047
25166
  }
25048
25167
  if (isRuntimeTensor(idx)) {
25168
+ if (idx._isLogical) {
25169
+ const newElements2 = [];
25170
+ for (let k = 0; k < idx.data.length; k++) {
25171
+ if (idx.data[k] !== 0) {
25172
+ if (k >= base.elements.length)
25173
+ throw new RuntimeError("Index exceeds struct array bounds");
25174
+ newElements2.push(base.elements[k]);
25175
+ }
25176
+ }
25177
+ return RTV.structArray(base.fieldNames, newElements2);
25178
+ }
25049
25179
  const newElements = [];
25050
25180
  for (let k = 0; k < idx.data.length; k++) {
25051
25181
  const i = Math.round(idx.data[k]) - 1;
@@ -38167,13 +38297,19 @@ registerIBuiltin({
38167
38297
  resolve: (argTypes) => {
38168
38298
  if (argTypes.length !== 1) return null;
38169
38299
  const a = argTypes[0];
38170
- if (a.kind !== "char" && a.kind !== "string" && a.kind !== "number" && a.kind !== "tensor" && a.kind !== "cell" && a.kind !== "unknown")
38300
+ if (a.kind !== "char" && a.kind !== "string" && a.kind !== "number" && a.kind !== "tensor" && a.kind !== "cell" && a.kind !== "unknown" && // datetime/duration only — other classes keep their own char methods
38301
+ // (or the generic "does not support these argument types" error).
38302
+ !(a.kind === "class_instance" && (a.className === "datetime" || a.className === "duration")))
38171
38303
  return null;
38172
38304
  return {
38173
38305
  outputTypes: [{ kind: "char" }],
38174
38306
  apply: (args) => {
38175
38307
  const v = args[0];
38176
38308
  if (isRuntimeChar(v)) return v;
38309
+ if (isRuntimeClassInstance(v)) {
38310
+ const s = formatDatetimeOrDuration(v);
38311
+ if (s !== null) return RTV.char(s);
38312
+ }
38177
38313
  if (isRuntimeString(v)) return RTV.char(v);
38178
38314
  if (isRuntimeNumber(v))
38179
38315
  return RTV.char(String.fromCharCode(Math.round(v)));
@@ -40561,7 +40697,7 @@ defineBuiltin({
40561
40697
  const result = invComplexJS(A.data, A.imag, n);
40562
40698
  return RTV.tensor(result.re, [n, n], result.im);
40563
40699
  }
40564
- const bridge = getEffectiveBridge("inv");
40700
+ const bridge = getEffectiveBridge("inv", "inv");
40565
40701
  return RTV.tensor(bridge.inv(toF64(A.data), n), [n, n]);
40566
40702
  }
40567
40703
  }
@@ -42908,6 +43044,16 @@ defineBuiltin({
42908
43044
  parseReshapeDims(args, 1);
42909
43045
  return v;
42910
43046
  }
43047
+ if (isRuntimeCell(v)) {
43048
+ const total = v.data.length;
43049
+ const s = parseReshapeDims(args, total);
43050
+ while (s.length > 2 && s[s.length - 1] === 1) s.pop();
43051
+ if (s.length > 2)
43052
+ throw new RuntimeError("reshape: cell arrays must be 2-D");
43053
+ const nr = s[0];
43054
+ const nc = s.length >= 2 ? s[1] : 1;
43055
+ return RTV.cell(v.data.slice(), [nr, nc]);
43056
+ }
42911
43057
  if (isRuntimeClassInstanceArray(v)) {
42912
43058
  const total = v.elements.length;
42913
43059
  const s = parseReshapeDims(args, total);
@@ -48654,6 +48800,32 @@ function datetimeFromUtcMs(ms) {
48654
48800
  function datetimeFromDatenum(dn) {
48655
48801
  return datetimeFromUtcMs((dn - MATLAB_EPOCH_DAYS) * 864e5);
48656
48802
  }
48803
+ function withTimeZoneFormat(dt, timeZone, format) {
48804
+ if (timeZone === void 0 && format === void 0) return dt;
48805
+ const fields = new Map(dt.fields);
48806
+ if (timeZone !== void 0) fields.set("TimeZone", RTV.char(timeZone));
48807
+ if (format !== void 0) fields.set("Format", RTV.char(format));
48808
+ return new RuntimeClassInstance("datetime", fields, false);
48809
+ }
48810
+ function todayParts(offsetDays, utc) {
48811
+ const now = /* @__PURE__ */ new Date();
48812
+ if (utc) {
48813
+ const d2 = new Date(
48814
+ Date.UTC(
48815
+ now.getUTCFullYear(),
48816
+ now.getUTCMonth(),
48817
+ now.getUTCDate() + offsetDays
48818
+ )
48819
+ );
48820
+ return [d2.getUTCFullYear(), d2.getUTCMonth() + 1, d2.getUTCDate()];
48821
+ }
48822
+ const d = new Date(
48823
+ now.getFullYear(),
48824
+ now.getMonth(),
48825
+ now.getDate() + offsetDays
48826
+ );
48827
+ return [d.getFullYear(), d.getMonth() + 1, d.getDate()];
48828
+ }
48657
48829
  function makeDuration(totalSeconds) {
48658
48830
  const fields = /* @__PURE__ */ new Map([
48659
48831
  ["Seconds", RTV.num(totalSeconds)]
@@ -48703,9 +48875,10 @@ registerIBuiltin({
48703
48875
  "t = datetime('now' | 'today' | 'yesterday' | 'tomorrow')",
48704
48876
  "t = datetime(Y, M, D)",
48705
48877
  "t = datetime(Y, M, D, H, MI, S)",
48706
- "t = datetime(X, 'ConvertFrom', dateType)"
48878
+ "t = datetime(X, 'ConvertFrom', dateType)",
48879
+ "t = datetime(..., 'TimeZone', tz, 'Format', fmt)"
48707
48880
  ],
48708
- description: "Create a datetime value. Supported dateType values for ConvertFrom: 'datenum', 'posixtime', 'excel', 'excel1904'."
48881
+ description: "Create a datetime value. Supported dateType values for ConvertFrom: 'datenum', 'posixtime', 'excel', 'excel1904'. Trailing 'TimeZone' (only '', 'local', 'UTC') and 'Format' name-value pairs are supported; the Format pattern is used by char() and display."
48709
48882
  },
48710
48883
  resolve: () => ({
48711
48884
  outputTypes: [
@@ -48717,75 +48890,83 @@ registerIBuiltin({
48717
48890
  }
48718
48891
  ],
48719
48892
  apply: (args) => {
48720
- if (args.length === 0) {
48721
- return datetimeFromDate(/* @__PURE__ */ new Date());
48893
+ let timeZone;
48894
+ let format;
48895
+ let positional = args;
48896
+ while (positional.length >= 2) {
48897
+ const name = argToString(positional[positional.length - 2]);
48898
+ if (name === null) break;
48899
+ const lc = name.toLowerCase();
48900
+ if (lc !== "timezone" && lc !== "format") break;
48901
+ const value = argToString(positional[positional.length - 1]);
48902
+ if (value === null)
48903
+ throw new RuntimeError(`datetime: ${name} value must be text`);
48904
+ if (lc === "timezone") timeZone = value;
48905
+ else format = value;
48906
+ positional = positional.slice(0, -2);
48907
+ }
48908
+ const tzLc = timeZone?.toLowerCase();
48909
+ if (tzLc !== void 0 && tzLc !== "" && tzLc !== "local" && tzLc !== "utc") {
48910
+ throw new RuntimeError(
48911
+ `datetime: unsupported TimeZone '${timeZone}' (supported: '', 'local', 'UTC')`
48912
+ );
48722
48913
  }
48723
- if (args.length === 1) {
48724
- const s = argToString(args[0]);
48914
+ const utc = tzLc === "utc";
48915
+ const finish = (dt) => withTimeZoneFormat(dt, timeZone, format);
48916
+ if (positional.length === 0) {
48917
+ return finish(
48918
+ utc ? datetimeFromUtcMs(Date.now()) : datetimeFromDate(/* @__PURE__ */ new Date())
48919
+ );
48920
+ }
48921
+ if (positional.length === 1) {
48922
+ const s = argToString(positional[0]);
48725
48923
  if (s !== null) {
48726
- const now = /* @__PURE__ */ new Date();
48727
48924
  const lc = s.toLowerCase();
48728
- if (lc === "now") return datetimeFromDate(now);
48729
- if (lc === "today") {
48730
- return makeDatetime(
48731
- now.getFullYear(),
48732
- now.getMonth() + 1,
48733
- now.getDate(),
48734
- 0,
48735
- 0,
48736
- 0
48925
+ if (lc === "now") {
48926
+ return finish(
48927
+ utc ? datetimeFromUtcMs(Date.now()) : datetimeFromDate(/* @__PURE__ */ new Date())
48737
48928
  );
48738
48929
  }
48739
- if (lc === "yesterday" || lc === "tomorrow") {
48740
- const d = new Date(
48741
- now.getFullYear(),
48742
- now.getMonth(),
48743
- now.getDate() + (lc === "tomorrow" ? 1 : -1)
48744
- );
48745
- return makeDatetime(
48746
- d.getFullYear(),
48747
- d.getMonth() + 1,
48748
- d.getDate(),
48749
- 0,
48750
- 0,
48751
- 0
48752
- );
48930
+ if (lc === "today" || lc === "yesterday" || lc === "tomorrow") {
48931
+ const offset = lc === "tomorrow" ? 1 : lc === "yesterday" ? -1 : 0;
48932
+ const [Y, M, D] = todayParts(offset, utc);
48933
+ return finish(makeDatetime(Y, M, D, 0, 0, 0));
48753
48934
  }
48754
48935
  throw new RuntimeError(
48755
48936
  `datetime: unrecognized relative-day string '${s}'`
48756
48937
  );
48757
48938
  }
48758
48939
  }
48759
- if (args.length === 3) {
48760
- const flag = argToString(args[1]);
48761
- const kind = argToString(args[2]);
48940
+ if (positional.length === 3) {
48941
+ const flag = argToString(positional[1]);
48942
+ const kind = argToString(positional[2]);
48762
48943
  if (flag !== null && flag.toLowerCase() === "convertfrom") {
48763
48944
  if (kind === null)
48764
48945
  throw new RuntimeError("datetime: dateType must be text");
48765
- const x = toNumber(args[0]);
48946
+ const x = toNumber(positional[0]);
48766
48947
  const k = kind.toLowerCase();
48767
- if (k === "datenum") return datetimeFromDatenum(x);
48768
- if (k === "posixtime") return datetimeFromUtcMs(x * 1e3);
48948
+ if (k === "datenum") return finish(datetimeFromDatenum(x));
48949
+ if (k === "posixtime") return finish(datetimeFromUtcMs(x * 1e3));
48769
48950
  if (k === "excel") {
48770
- return datetimeFromUtcMs((x - 25569) * 864e5);
48951
+ return finish(datetimeFromUtcMs((x - 25569) * 864e5));
48771
48952
  }
48772
48953
  if (k === "excel1904") {
48773
- return datetimeFromUtcMs((x - 24107) * 864e5);
48954
+ return finish(datetimeFromUtcMs((x - 24107) * 864e5));
48774
48955
  }
48775
48956
  throw new RuntimeError(
48776
48957
  `datetime: unsupported ConvertFrom type '${kind}'`
48777
48958
  );
48778
48959
  }
48779
48960
  }
48780
- if (args.length === 3 || args.length === 6 || args.length === 7) {
48781
- const Y = toNumber(args[0]);
48782
- const M = toNumber(args[1]);
48783
- const D = toNumber(args[2]);
48784
- const H2 = args.length >= 6 ? toNumber(args[3]) : 0;
48785
- const MI = args.length >= 6 ? toNumber(args[4]) : 0;
48786
- const S = args.length >= 6 ? toNumber(args[5]) : 0;
48787
- const MS = args.length >= 7 ? toNumber(args[6]) : 0;
48788
- return makeDatetime(Y, M, D, H2, MI, S + MS / 1e3);
48961
+ if (positional.length === 3 || positional.length === 6 || positional.length === 7) {
48962
+ const Y = toNumber(positional[0]);
48963
+ const M = toNumber(positional[1]);
48964
+ const D = toNumber(positional[2]);
48965
+ const H2 = positional.length >= 6 ? toNumber(positional[3]) : 0;
48966
+ const MI = positional.length >= 6 ? toNumber(positional[4]) : 0;
48967
+ const S = positional.length >= 6 ? toNumber(positional[5]) : 0;
48968
+ const MS = positional.length >= 7 ? toNumber(positional[6]) : 0;
48969
+ return finish(makeDatetime(Y, M, D, H2, MI, S + MS / 1e3));
48789
48970
  }
48790
48971
  throw new RuntimeError(
48791
48972
  `datetime: unsupported argument pattern (nargin=${args.length})`
@@ -56090,6 +56271,7 @@ var SPECIAL_BUILTIN_NAMES = [
56090
56271
  "copyfile",
56091
56272
  "fileattrib",
56092
56273
  "unzip",
56274
+ "zip",
56093
56275
  "dir",
56094
56276
  "warning",
56095
56277
  "input",
@@ -57202,6 +57384,26 @@ function registerSpecialBuiltins(rt) {
57202
57384
  }
57203
57385
  return void 0;
57204
57386
  });
57387
+ registerSpecial("zip", (nargout, args) => {
57388
+ const io = requireFileIO();
57389
+ if (!io.zip)
57390
+ throw new RuntimeError("zip is not available in this environment");
57391
+ const margs = args.map((a) => ensureRuntimeValue(a));
57392
+ if (margs.length < 2)
57393
+ throw new RuntimeError("zip requires at least 2 arguments");
57394
+ let zipfilename = toString(margs[0]);
57395
+ const base = zipfilename.split(/[/\\]/).pop() ?? zipfilename;
57396
+ if (!base.includes(".")) zipfilename += ".zip";
57397
+ const fnames = margs[1];
57398
+ const filenames = isRuntimeCell(fnames) ? fnames.data.map((v) => toString(ensureRuntimeValue(v))) : [toString(fnames)];
57399
+ const rootdir = margs.length >= 3 ? toString(margs[2]) : ".";
57400
+ const entryNames = io.zip(zipfilename, filenames, rootdir);
57401
+ if (nargout >= 1) {
57402
+ const cellData = entryNames.map((f) => RTV.char(f));
57403
+ return RTV.cell(cellData, [1, cellData.length]);
57404
+ }
57405
+ return void 0;
57406
+ });
57205
57407
  registerSpecial("dir", (nargout, args) => {
57206
57408
  const io = requireFileIO();
57207
57409
  if (!io.listDir)
@@ -61069,8 +61271,8 @@ function padExtent(e, tight) {
61069
61271
  return [min2, max2];
61070
61272
  }
61071
61273
  if (tight) return [min2, max2];
61072
- const pad = (max2 - min2) * 0.05;
61073
- return [min2 - pad, max2 + pad];
61274
+ const pad2 = (max2 - min2) * 0.05;
61275
+ return [min2 - pad2, max2 + pad2];
61074
61276
  }
61075
61277
  function applyExplicit(auto, explicit) {
61076
61278
  if (!explicit) return auto;
@@ -65895,6 +66097,12 @@ function evalMember(expr, nargout) {
65895
66097
  }
65896
66098
  }
65897
66099
  const base = this.evalExpr(expr.base);
66100
+ const baseRv = ensureRuntimeValue(base);
66101
+ if (isRuntimeStructArray(baseRv) && baseRv.elements.length !== 1) {
66102
+ return baseRv.elements.map(
66103
+ (el) => ensureRuntimeValue(this.rt.getMember(el, expr.name))
66104
+ );
66105
+ }
65898
66106
  return this.rt.getMember(base, expr.name);
65899
66107
  }
65900
66108
  function tryExtractDottedName(expr) {
@@ -66106,12 +66314,31 @@ function evalCellLiteral(expr) {
66106
66314
  }
66107
66315
  return RTV.cell(elements2, [1, elements2.length]);
66108
66316
  }
66109
- const numRows = expr.rows.length;
66110
- const numCols = expr.rows[0].length;
66317
+ const rowValues = expr.rows.map((row) => {
66318
+ const vals = [];
66319
+ for (const e of row) {
66320
+ const v = this.evalExpr(e);
66321
+ if (Array.isArray(v)) {
66322
+ for (const elem of v) vals.push(ensureRuntimeValue(elem));
66323
+ } else {
66324
+ vals.push(ensureRuntimeValue(v));
66325
+ }
66326
+ }
66327
+ return vals;
66328
+ });
66329
+ const numRows = rowValues.length;
66330
+ const numCols = rowValues[0].length;
66331
+ for (const rv of rowValues) {
66332
+ if (rv.length !== numCols) {
66333
+ throw new RuntimeError(
66334
+ "Cell array rows must have the same number of columns"
66335
+ );
66336
+ }
66337
+ }
66111
66338
  const elements = [];
66112
66339
  for (let c = 0; c < numCols; c++) {
66113
66340
  for (let r = 0; r < numRows; r++) {
66114
- elements.push(ensureRuntimeValue(this.evalExpr(expr.rows[r][c])));
66341
+ elements.push(rowValues[r][c]);
66115
66342
  }
66116
66343
  }
66117
66344
  return RTV.cell(elements, [numRows, numCols]);
@@ -70586,8 +70813,8 @@ function mtoc2_sprintf_format(fmt, args) {
70586
70813
  if (width > 0) {
70587
70814
  const padChar = zeroPad ? "0" : " ";
70588
70815
  const padLen = Math.max(0, width - sign.length - s.length);
70589
- const pad = padChar.repeat(padLen);
70590
- result += leftAlign ? sign + s + " ".repeat(padLen) : zeroPad ? sign + pad + s : pad + sign + s;
70816
+ const pad2 = padChar.repeat(padLen);
70817
+ result += leftAlign ? sign + s + " ".repeat(padLen) : zeroPad ? sign + pad2 + s : pad2 + sign + s;
70591
70818
  } else {
70592
70819
  result += sign + s;
70593
70820
  }
@@ -70689,6 +70916,24 @@ function mtoc2_sprintf_format(fmt, args) {
70689
70916
  case "t":
70690
70917
  result += " ";
70691
70918
  break;
70919
+ case "r":
70920
+ result += "\r";
70921
+ break;
70922
+ case "a":
70923
+ result += "\x07";
70924
+ break;
70925
+ case "b":
70926
+ result += "\b";
70927
+ break;
70928
+ case "f":
70929
+ result += "\f";
70930
+ break;
70931
+ case "v":
70932
+ result += "\v";
70933
+ break;
70934
+ case "0":
70935
+ result += "\0";
70936
+ break;
70692
70937
  case "\\":
70693
70938
  result += "\\";
70694
70939
  break;
@@ -70872,8 +71117,8 @@ function disp_complex_slice(re, im, offset, rows, cols) {
70872
71117
  for (let c = 0; c < cols; c++) {
70873
71118
  const idx = r + c * rows;
70874
71119
  const cell = cells[idx];
70875
- const pad = colWidths[c] - cell.length;
70876
- for (let i = 0; i < pad; i++) line += " ";
71120
+ const pad2 = colWidths[c] - cell.length;
71121
+ for (let i = 0; i < pad2; i++) line += " ";
70877
71122
  line += cell;
70878
71123
  if (c < cols - 1) line += " ";
70879
71124
  }
@@ -72285,6 +72530,14 @@ function mtoc2_tensor_mtimes_real(a, b) {
72285
72530
  `mtoc2_tensor_mtimes_real: inner dimensions disagree (${k} vs ${k2})`
72286
72531
  );
72287
72532
  }
72533
+ if (typeof $matmulAccel === "function") {
72534
+ const accel = $matmulAccel(a.data, m, k, b.data, n);
72535
+ if (accel) {
72536
+ const acc = mtoc2_tensor_alloc(m, n);
72537
+ acc.data.set(accel);
72538
+ return acc;
72539
+ }
72540
+ }
72288
72541
  const r = mtoc2_tensor_alloc(m, n);
72289
72542
  for (let j = 0; j < n; j++) {
72290
72543
  for (let i = 0; i < m; i++) {
@@ -73458,7 +73711,7 @@ var JS_SNIPPETS = {
73458
73711
  "error_fmt.js": "// JS sibling of `error_fmt.h`. Format the message + arguments and\n// throw a runtime error. Mirrors numbl's `error(...)` behavior\n// (stderr + halt).\n//\n// The thrown error carries an `identifier` string property so a\n// `try / catch ME` arm in the interpreter can populate the `ME`\n// struct's `identifier` field. When the caller passes no identifier\n// (the common `error('msg')` form), it's the empty string.\n\n\nfunction mtoc2_error_fmt(fmt, ...args) {\n const msg = mtoc2_sprintf_format(fmt, args);\n // Numbl writes to stderr; in the JS host we route through the same\n // `$write` channel by emitting a \"stderr\" sentinel prefix the host\n // can split on. For now, throw \u2014 the CLI's runner formats the\n // exception to stderr, matching the user-visible result.\n const err = new Error(msg);\n err.identifier = \"\";\n throw err;\n}\n\nfunction mtoc2_error_fmt_id(identifier, fmt, ...args) {\n const msg = mtoc2_sprintf_format(fmt, args);\n const err = new Error(msg);\n err.identifier = identifier;\n throw err;\n}\n",
73459
73712
  "format_complex.js": '// JS sibling of `format_complex.h`. Format a scalar complex `{re, im}`\n// the way numbl\'s `formatComplex` does. Each component is rendered\n// through `mtoc2_format_double` so the scalar formatting stays in\n// lockstep with numbl\'s `formatNumber`.\n\n\nfunction mtoc2_format_complex(z) {\n const re = z.re;\n const im = z.im;\n if (im === 0) return mtoc2_format_double(re);\n if (re === 0) return mtoc2_format_double(im) + "i";\n if (im < 0) {\n return mtoc2_format_double(re) + " - " + mtoc2_format_double(-im) + "i";\n }\n return mtoc2_format_double(re) + " + " + mtoc2_format_double(im) + "i";\n}\n',
73460
73713
  "format_double.js": '// JS sibling of `format_double.h` \u2014 same numbl-compatible formatter,\n// in JS. Used by `emitJs` for the `disp` scalar path and (later) by\n// the interpreter via the re-export in `snippets.gen.ts`.\n\nfunction mtoc2_format_double(x) {\n if (Number.isNaN(x)) return "NaN";\n // MATLAB spells these Inf / -Inf (matches the interpreter\'s formatNumber).\n if (x === Infinity) return "Inf";\n if (x === -Infinity) return "-Inf";\n if (x === 0) x = 0;\n if (Math.abs(x) < 1e15 && x === Math.trunc(x)) return String(x);\n\n const buf = x.toExponential(4);\n const ePos = buf.indexOf("e");\n if (ePos < 0) return buf;\n const decExp = parseInt(buf.slice(ePos + 1), 10);\n\n if (decExp < -6 || decExp >= 5) {\n let mantissaEnd = ePos - 1;\n while (mantissaEnd > 0 && buf[mantissaEnd] === "0") mantissaEnd--;\n if (mantissaEnd >= 0 && buf[mantissaEnd] === ".") mantissaEnd--;\n let expPart = buf.slice(ePos + 1);\n let sign = "+";\n if (expPart[0] === "+" || expPart[0] === "-") {\n sign = expPart[0];\n expPart = expPart.slice(1);\n }\n while (expPart.length > 1 && expPart[0] === "0") expPart = expPart.slice(1);\n return buf.slice(0, mantissaEnd + 1) + "e" + sign + expPart;\n }\n\n let fracDigits = 4 - decExp;\n if (fracDigits < 0) fracDigits = 0;\n let s = x.toFixed(fracDigits);\n if (s.includes(".")) {\n while (s.endsWith("0")) s = s.slice(0, -1);\n if (s.endsWith(".")) s = s.slice(0, -1);\n }\n return s;\n}\n',
73461
- "format_engine.js": '// JS sibling of `format_engine.h`. Numbl-compatible printf-style\n// formatter, ported from `numbl/src/numbl-core/helpers/string.ts`\'s\n// `sprintfFormat`. Shared by fprintf / sprintf / error / assert on\n// the JS side.\n//\n// Spec set: d i u f e E g G x X o s c %\n// Flags: - + 0 # space. Precision `.N`. Width digits. `*` consumes the\n// next numeric arg as a width.\n// Escapes interpreted AT FORMAT TIME: \\n \\t \\\\.\n// Numeric tensors flatten column-major into the scalar stream; the\n// format string cycles through args until consumed (with a "no arg\n// consumed this pass" guard against infinite output).\n\nfunction isTensor(v) {\n return typeof v === "object" && v !== null && v.mtoc2Tag === "tensor";\n}\nfunction isChar(v) {\n return typeof v === "object" && v !== null && v.mtoc2Tag === "char";\n}\n\nfunction toNumber(v) {\n if (typeof v === "number") return v;\n if (typeof v === "boolean") return v ? 1 : 0;\n if (typeof v === "string") return Number(v);\n if (isChar(v)) {\n // Match the interpreter\'s toNumber (runtime/convert.ts): a single\n // char yields its code point; a multi-char value has no scalar\n // numeric form. `Number("A")` would wrongly give NaN.\n if (v.value.length === 1) return v.value.charCodeAt(0);\n throw new Error("Cannot convert multi-char to number");\n }\n if (isTensor(v) && v.data.length === 1) return v.data[0];\n return Number(v);\n}\n\nfunction toString(v) {\n if (typeof v === "string") return v;\n if (typeof v === "number") return String(v);\n if (typeof v === "boolean") return v ? "1" : "0";\n if (isChar(v)) return v.value;\n return String(v);\n}\n\nfunction numStr(n) {\n if (n === Infinity) return "Inf";\n if (n === -Infinity) return "-Inf";\n if (Number.isNaN(n)) return "NaN";\n if (n === 0) return "0";\n const prec = 5;\n const exp = Math.floor(Math.log10(Math.abs(n)));\n let s;\n if (exp < -4 || exp >= prec) {\n s = n.toExponential(prec - 1);\n const ePos = s.indexOf("e");\n let mantissa = s.slice(0, ePos);\n const expPart0 = s.slice(ePos);\n if (mantissa.includes(".")) mantissa = mantissa.replace(/\\.?0+$/, "");\n const expPart = expPart0.replace(/([eE][+-])(\\d)$/, "$1" + "0$2");\n s = mantissa + expPart;\n } else {\n if (Number.isInteger(n)) return String(n);\n s = n.toPrecision(prec);\n if (s.includes(".")) s = s.replace(/\\.?0+$/, "");\n }\n return s;\n}\n\nfunction applyWidth(spec, str) {\n const m = spec.match(/^%([-+ #]*)0?(\\d+)?/);\n if (!m) return str;\n const explicitFlags = m[1] || "";\n const leftAlign = explicitFlags.includes("-");\n const afterPercent = spec.slice(1);\n const flagAndWidth = afterPercent.match(/^([-+ #]*)(0?)(\\d+)?/);\n const zeroFlag = flagAndWidth ? flagAndWidth[2] === "0" : false;\n const width = flagAndWidth && flagAndWidth[3] ? parseInt(flagAndWidth[3]) : 0;\n if (width <= str.length) return str;\n const zeroPad = !leftAlign && zeroFlag;\n const padLen = width - str.length;\n if (leftAlign) return str + " ".repeat(padLen);\n if (zeroPad) {\n if (str[0] === "-" || str[0] === "+") {\n return str[0] + "0".repeat(padLen) + str.slice(1);\n }\n return "0".repeat(padLen) + str;\n }\n return " ".repeat(padLen) + str;\n}\n\nfunction mtoc2_sprintf_format(fmt, args) {\n // Char literals (`\'foo\'`) come through wrapped as\n // `{mtoc2Tag:"char", value:"..."}` to match the interpreter\'s\n // RuntimeValue convention; unwrap so the format walker indexes\n // a plain JS string.\n if (isChar(fmt)) fmt = fmt.value;\n // Flatten args column-major (tensors expand element-by-element).\n const flat = [];\n for (const arg of args) {\n if (isTensor(arg)) {\n for (let k = 0; k < arg.data.length; k++) flat.push(arg.data[k]);\n } else {\n flat.push(arg);\n }\n }\n\n let result = "";\n let argIdx = 0;\n\n do {\n const startArgIdx = argIdx;\n let outOfArgs = false;\n let i = 0;\n while (i < fmt.length && !outOfArgs) {\n if (fmt[i] === "%" && i + 1 < fmt.length) {\n i++;\n let spec = "%";\n while (i < fmt.length && !"dfigeEsoxXuc%".includes(fmt[i])) {\n if (fmt[i] === "*") {\n if (argIdx >= flat.length) {\n outOfArgs = true;\n break;\n }\n spec += String(Math.round(toNumber(flat[argIdx++])));\n i++;\n } else {\n spec += fmt[i];\n i++;\n }\n }\n if (outOfArgs) break;\n if (i < fmt.length) {\n const ch = fmt[i];\n i++;\n if (ch === "%") {\n result += "%";\n } else if (argIdx >= flat.length) {\n outOfArgs = true;\n } else if (ch === "d" || ch === "i" || ch === "u") {\n const raw = toNumber(flat[argIdx++]);\n const isInt = Number.isInteger(raw);\n const canPrintAsInt = ch === "u" ? isInt && raw >= 0 : isInt;\n if (!canPrintAsInt) {\n let eStr = raw.toExponential(6);\n eStr = eStr.replace(/e([+-])(\\d)$/, "e$1" + "0$2");\n result += applyWidth(spec, eStr);\n } else {\n const n = raw;\n const flags = spec.slice(1);\n const hasPlus = flags.includes("+");\n const leftAlign = flags.includes("-");\n const widthMatch = spec.match(/^%[^0-9]*(\\d+)/);\n const width = widthMatch ? parseInt(widthMatch[1]) : 0;\n const zeroPad = !leftAlign && /^[-+ ]*0/.test(spec.slice(1));\n const s = String(Math.abs(n));\n const sign = n < 0 ? "-" : hasPlus ? "+" : "";\n if (width > 0) {\n const padChar = zeroPad ? "0" : " ";\n const padLen = Math.max(0, width - sign.length - s.length);\n const pad = padChar.repeat(padLen);\n result += leftAlign\n ? sign + s + " ".repeat(padLen)\n : zeroPad\n ? sign + pad + s\n : pad + sign + s;\n } else {\n result += sign + s;\n }\n }\n } else if (ch === "f") {\n const n = toNumber(flat[argIdx++]);\n if (!isFinite(n) || isNaN(n)) {\n result += applyWidth(spec, numStr(n));\n } else {\n const fFlags = spec.slice(1);\n const fHasPlus = fFlags.includes("+");\n const precMatch = spec.match(/\\.(\\d+)/);\n const prec = precMatch ? parseInt(precMatch[1]) : 6;\n const formatted = n.toFixed(prec);\n const fSign = n < 0 ? "" : fHasPlus ? "+" : "";\n result += applyWidth(spec, fSign + formatted);\n }\n } else if (ch === "e" || ch === "E") {\n const n = toNumber(flat[argIdx++]);\n if (!isFinite(n) || isNaN(n)) {\n result += applyWidth(spec, numStr(n));\n } else {\n const precMatch = spec.match(/\\.(\\d+)/);\n const prec = precMatch ? parseInt(precMatch[1]) : 6;\n let eStr = n.toExponential(prec);\n eStr = eStr.replace(/e([+-])(\\d)$/, "e$1" + "0$2");\n if (ch === "E") eStr = eStr.toUpperCase();\n result += applyWidth(spec, eStr);\n }\n } else if (ch === "x" || ch === "X") {\n const n = Math.round(toNumber(flat[argIdx++]));\n let s = Math.abs(n).toString(16);\n if (ch === "X") s = s.toUpperCase();\n result += applyWidth(spec, s);\n } else if (ch === "o") {\n const n = Math.round(toNumber(flat[argIdx++]));\n result += applyWidth(spec, Math.abs(n).toString(8));\n } else if (ch === "g" || ch === "G") {\n const gVal = toNumber(flat[argIdx++]);\n if (!isFinite(gVal) || isNaN(gVal)) {\n result += applyWidth(spec, numStr(gVal));\n } else {\n const precMatch = spec.match(/\\.(\\d+)/);\n const gPrec = precMatch ? parseInt(precMatch[1]) : 6;\n let gStr;\n if (gVal === 0) {\n gStr = "0";\n } else {\n const exp = Math.floor(Math.log10(Math.abs(gVal)));\n if (exp < -4 || exp >= gPrec) {\n gStr = gVal.toExponential(gPrec - 1);\n const ePos = gStr.indexOf("e");\n let mantissa = gStr.slice(0, ePos);\n let expPart = gStr.slice(ePos);\n if (mantissa.includes(".")) {\n mantissa = mantissa.replace(/\\.?0+$/, "");\n }\n expPart = expPart.replace(/e([+-])(\\d)$/, "e$1" + "0$2");\n gStr = mantissa + expPart;\n } else {\n gStr = gVal.toPrecision(gPrec);\n if (gStr.includes(".")) {\n gStr = gStr.replace(/\\.?0+$/, "");\n }\n if (gStr.includes("e")) {\n gStr = String(parseFloat(gStr));\n }\n }\n }\n if (ch === "G") gStr = gStr.toUpperCase();\n result += applyWidth(spec, gStr);\n }\n } else if (ch === "s") {\n const sArg = flat[argIdx++];\n // A numeric/logical argument to %s is a character code (MATLAB:\n // sprintf(\'%s\', 65) -> \'A\').\n const sVal =\n typeof sArg === "number" || typeof sArg === "boolean"\n ? String.fromCharCode(Math.round(toNumber(sArg)))\n : toString(sArg);\n const sFlags = spec.slice(1);\n const sLeftAlign = sFlags.includes("-");\n const sWidthMatch = spec.match(/^%[^0-9]*(\\d+)/);\n const sWidth = sWidthMatch ? parseInt(sWidthMatch[1]) : 0;\n if (sWidth > sVal.length) {\n const sPad = " ".repeat(sWidth - sVal.length);\n result += sLeftAlign ? sVal + sPad : sPad + sVal;\n } else {\n result += sVal;\n }\n } else if (ch === "c") {\n result += String.fromCharCode(Math.round(toNumber(flat[argIdx++])));\n } else {\n result += spec + ch;\n argIdx++;\n }\n }\n } else if (fmt[i] === "\\\\" && i + 1 < fmt.length) {\n i++;\n switch (fmt[i]) {\n case "n":\n result += "\\n";\n break;\n case "t":\n result += "\\t";\n break;\n case "\\\\":\n result += "\\\\";\n break;\n default:\n result += "\\\\" + fmt[i];\n }\n i++;\n } else {\n result += fmt[i];\n i++;\n }\n }\n if (argIdx === startArgIdx) break;\n } while (argIdx < flat.length);\n\n return result;\n}\n',
73714
+ "format_engine.js": '// JS sibling of `format_engine.h`. Numbl-compatible printf-style\n// formatter, ported from `numbl/src/numbl-core/helpers/string.ts`\'s\n// `sprintfFormat`. Shared by fprintf / sprintf / error / assert on\n// the JS side.\n//\n// Spec set: d i u f e E g G x X o s c %\n// Flags: - + 0 # space. Precision `.N`. Width digits. `*` consumes the\n// next numeric arg as a width.\n// Escapes interpreted AT FORMAT TIME: \\n \\t \\r \\a \\b \\f \\v \\0 \\\\.\n// Numeric tensors flatten column-major into the scalar stream; the\n// format string cycles through args until consumed (with a "no arg\n// consumed this pass" guard against infinite output).\n\nfunction isTensor(v) {\n return typeof v === "object" && v !== null && v.mtoc2Tag === "tensor";\n}\nfunction isChar(v) {\n return typeof v === "object" && v !== null && v.mtoc2Tag === "char";\n}\n\nfunction toNumber(v) {\n if (typeof v === "number") return v;\n if (typeof v === "boolean") return v ? 1 : 0;\n if (typeof v === "string") return Number(v);\n if (isChar(v)) {\n // Match the interpreter\'s toNumber (runtime/convert.ts): a single\n // char yields its code point; a multi-char value has no scalar\n // numeric form. `Number("A")` would wrongly give NaN.\n if (v.value.length === 1) return v.value.charCodeAt(0);\n throw new Error("Cannot convert multi-char to number");\n }\n if (isTensor(v) && v.data.length === 1) return v.data[0];\n return Number(v);\n}\n\nfunction toString(v) {\n if (typeof v === "string") return v;\n if (typeof v === "number") return String(v);\n if (typeof v === "boolean") return v ? "1" : "0";\n if (isChar(v)) return v.value;\n return String(v);\n}\n\nfunction numStr(n) {\n if (n === Infinity) return "Inf";\n if (n === -Infinity) return "-Inf";\n if (Number.isNaN(n)) return "NaN";\n if (n === 0) return "0";\n const prec = 5;\n const exp = Math.floor(Math.log10(Math.abs(n)));\n let s;\n if (exp < -4 || exp >= prec) {\n s = n.toExponential(prec - 1);\n const ePos = s.indexOf("e");\n let mantissa = s.slice(0, ePos);\n const expPart0 = s.slice(ePos);\n if (mantissa.includes(".")) mantissa = mantissa.replace(/\\.?0+$/, "");\n const expPart = expPart0.replace(/([eE][+-])(\\d)$/, "$1" + "0$2");\n s = mantissa + expPart;\n } else {\n if (Number.isInteger(n)) return String(n);\n s = n.toPrecision(prec);\n if (s.includes(".")) s = s.replace(/\\.?0+$/, "");\n }\n return s;\n}\n\nfunction applyWidth(spec, str) {\n const m = spec.match(/^%([-+ #]*)0?(\\d+)?/);\n if (!m) return str;\n const explicitFlags = m[1] || "";\n const leftAlign = explicitFlags.includes("-");\n const afterPercent = spec.slice(1);\n const flagAndWidth = afterPercent.match(/^([-+ #]*)(0?)(\\d+)?/);\n const zeroFlag = flagAndWidth ? flagAndWidth[2] === "0" : false;\n const width = flagAndWidth && flagAndWidth[3] ? parseInt(flagAndWidth[3]) : 0;\n if (width <= str.length) return str;\n const zeroPad = !leftAlign && zeroFlag;\n const padLen = width - str.length;\n if (leftAlign) return str + " ".repeat(padLen);\n if (zeroPad) {\n if (str[0] === "-" || str[0] === "+") {\n return str[0] + "0".repeat(padLen) + str.slice(1);\n }\n return "0".repeat(padLen) + str;\n }\n return " ".repeat(padLen) + str;\n}\n\nfunction mtoc2_sprintf_format(fmt, args) {\n // Char literals (`\'foo\'`) come through wrapped as\n // `{mtoc2Tag:"char", value:"..."}` to match the interpreter\'s\n // RuntimeValue convention; unwrap so the format walker indexes\n // a plain JS string.\n if (isChar(fmt)) fmt = fmt.value;\n // Flatten args column-major (tensors expand element-by-element).\n const flat = [];\n for (const arg of args) {\n if (isTensor(arg)) {\n for (let k = 0; k < arg.data.length; k++) flat.push(arg.data[k]);\n } else {\n flat.push(arg);\n }\n }\n\n let result = "";\n let argIdx = 0;\n\n do {\n const startArgIdx = argIdx;\n let outOfArgs = false;\n let i = 0;\n while (i < fmt.length && !outOfArgs) {\n if (fmt[i] === "%" && i + 1 < fmt.length) {\n i++;\n let spec = "%";\n while (i < fmt.length && !"dfigeEsoxXuc%".includes(fmt[i])) {\n if (fmt[i] === "*") {\n if (argIdx >= flat.length) {\n outOfArgs = true;\n break;\n }\n spec += String(Math.round(toNumber(flat[argIdx++])));\n i++;\n } else {\n spec += fmt[i];\n i++;\n }\n }\n if (outOfArgs) break;\n if (i < fmt.length) {\n const ch = fmt[i];\n i++;\n if (ch === "%") {\n result += "%";\n } else if (argIdx >= flat.length) {\n outOfArgs = true;\n } else if (ch === "d" || ch === "i" || ch === "u") {\n const raw = toNumber(flat[argIdx++]);\n const isInt = Number.isInteger(raw);\n const canPrintAsInt = ch === "u" ? isInt && raw >= 0 : isInt;\n if (!canPrintAsInt) {\n let eStr = raw.toExponential(6);\n eStr = eStr.replace(/e([+-])(\\d)$/, "e$1" + "0$2");\n result += applyWidth(spec, eStr);\n } else {\n const n = raw;\n const flags = spec.slice(1);\n const hasPlus = flags.includes("+");\n const leftAlign = flags.includes("-");\n const widthMatch = spec.match(/^%[^0-9]*(\\d+)/);\n const width = widthMatch ? parseInt(widthMatch[1]) : 0;\n const zeroPad = !leftAlign && /^[-+ ]*0/.test(spec.slice(1));\n const s = String(Math.abs(n));\n const sign = n < 0 ? "-" : hasPlus ? "+" : "";\n if (width > 0) {\n const padChar = zeroPad ? "0" : " ";\n const padLen = Math.max(0, width - sign.length - s.length);\n const pad = padChar.repeat(padLen);\n result += leftAlign\n ? sign + s + " ".repeat(padLen)\n : zeroPad\n ? sign + pad + s\n : pad + sign + s;\n } else {\n result += sign + s;\n }\n }\n } else if (ch === "f") {\n const n = toNumber(flat[argIdx++]);\n if (!isFinite(n) || isNaN(n)) {\n result += applyWidth(spec, numStr(n));\n } else {\n const fFlags = spec.slice(1);\n const fHasPlus = fFlags.includes("+");\n const precMatch = spec.match(/\\.(\\d+)/);\n const prec = precMatch ? parseInt(precMatch[1]) : 6;\n const formatted = n.toFixed(prec);\n const fSign = n < 0 ? "" : fHasPlus ? "+" : "";\n result += applyWidth(spec, fSign + formatted);\n }\n } else if (ch === "e" || ch === "E") {\n const n = toNumber(flat[argIdx++]);\n if (!isFinite(n) || isNaN(n)) {\n result += applyWidth(spec, numStr(n));\n } else {\n const precMatch = spec.match(/\\.(\\d+)/);\n const prec = precMatch ? parseInt(precMatch[1]) : 6;\n let eStr = n.toExponential(prec);\n eStr = eStr.replace(/e([+-])(\\d)$/, "e$1" + "0$2");\n if (ch === "E") eStr = eStr.toUpperCase();\n result += applyWidth(spec, eStr);\n }\n } else if (ch === "x" || ch === "X") {\n const n = Math.round(toNumber(flat[argIdx++]));\n let s = Math.abs(n).toString(16);\n if (ch === "X") s = s.toUpperCase();\n result += applyWidth(spec, s);\n } else if (ch === "o") {\n const n = Math.round(toNumber(flat[argIdx++]));\n result += applyWidth(spec, Math.abs(n).toString(8));\n } else if (ch === "g" || ch === "G") {\n const gVal = toNumber(flat[argIdx++]);\n if (!isFinite(gVal) || isNaN(gVal)) {\n result += applyWidth(spec, numStr(gVal));\n } else {\n const precMatch = spec.match(/\\.(\\d+)/);\n const gPrec = precMatch ? parseInt(precMatch[1]) : 6;\n let gStr;\n if (gVal === 0) {\n gStr = "0";\n } else {\n const exp = Math.floor(Math.log10(Math.abs(gVal)));\n if (exp < -4 || exp >= gPrec) {\n gStr = gVal.toExponential(gPrec - 1);\n const ePos = gStr.indexOf("e");\n let mantissa = gStr.slice(0, ePos);\n let expPart = gStr.slice(ePos);\n if (mantissa.includes(".")) {\n mantissa = mantissa.replace(/\\.?0+$/, "");\n }\n expPart = expPart.replace(/e([+-])(\\d)$/, "e$1" + "0$2");\n gStr = mantissa + expPart;\n } else {\n gStr = gVal.toPrecision(gPrec);\n if (gStr.includes(".")) {\n gStr = gStr.replace(/\\.?0+$/, "");\n }\n if (gStr.includes("e")) {\n gStr = String(parseFloat(gStr));\n }\n }\n }\n if (ch === "G") gStr = gStr.toUpperCase();\n result += applyWidth(spec, gStr);\n }\n } else if (ch === "s") {\n const sArg = flat[argIdx++];\n // A numeric/logical argument to %s is a character code (MATLAB:\n // sprintf(\'%s\', 65) -> \'A\').\n const sVal =\n typeof sArg === "number" || typeof sArg === "boolean"\n ? String.fromCharCode(Math.round(toNumber(sArg)))\n : toString(sArg);\n const sFlags = spec.slice(1);\n const sLeftAlign = sFlags.includes("-");\n const sWidthMatch = spec.match(/^%[^0-9]*(\\d+)/);\n const sWidth = sWidthMatch ? parseInt(sWidthMatch[1]) : 0;\n if (sWidth > sVal.length) {\n const sPad = " ".repeat(sWidth - sVal.length);\n result += sLeftAlign ? sVal + sPad : sPad + sVal;\n } else {\n result += sVal;\n }\n } else if (ch === "c") {\n result += String.fromCharCode(Math.round(toNumber(flat[argIdx++])));\n } else {\n result += spec + ch;\n argIdx++;\n }\n }\n } else if (fmt[i] === "\\\\" && i + 1 < fmt.length) {\n i++;\n switch (fmt[i]) {\n case "n":\n result += "\\n";\n break;\n case "t":\n result += "\\t";\n break;\n case "r":\n result += "\\r";\n break;\n case "a":\n result += "\\x07";\n break;\n case "b":\n result += "\\b";\n break;\n case "f":\n result += "\\f";\n break;\n case "v":\n result += "\\v";\n break;\n case "0":\n result += "\\0";\n break;\n case "\\\\":\n result += "\\\\";\n break;\n default:\n result += "\\\\" + fmt[i];\n }\n i++;\n } else {\n result += fmt[i];\n i++;\n }\n }\n if (argIdx === startArgIdx) break;\n } while (argIdx < flat.length);\n\n return result;\n}\n',
73462
73715
  "fprintf.js": "// JS sibling of `fprintf.h`. Routes formatted output through\n// `mtoc2_sprintf_format` then writes via `$write` (the JS analog of\n// C's stdout). Numbl-compatible spec set & semantics.\n\n\nfunction mtoc2_fprintf(fmt, ...args) {\n $write(mtoc2_sprintf_format(fmt, args));\n}\n",
73463
73716
  "sprintf.js": '// JS sibling of `sprintf.h`. Two output variants:\n// - `mtoc2_sprintf_str(fmt, args)` returns the formatted text as a\n// plain JS string (the equivalent of numbl\'s `String` kind).\n// - `mtoc2_sprintf_char(fmt, args)` returns it as a `mtoc2_char_tensor_t`\n// placeholder \u2014 we keep the JS shape as a `{mtoc2Tag: "char", value}`\n// object so disp/length etc. dispatch correctly.\n\n\nfunction mtoc2_sprintf_str(fmt, ...args) {\n return mtoc2_sprintf_format(fmt, args);\n}\n\nfunction mtoc2_sprintf_char(fmt, ...args) {\n return { mtoc2Tag: "char", value: mtoc2_sprintf_format(fmt, args) };\n}\n',
73464
73717
  "warning_fmt.js": '// JS sibling of `warning_fmt.h`. Format the message and write\n// `Warning: <msg>\\n` via `$write`. Matches numbl\n// (`runtime/specialBuiltins.ts:190` \u2014 `rt.output("Warning: " + msg + "\\n")`).\n\n\nfunction mtoc2_warning_fmt(fmt, ...args) {\n $write("Warning: " + mtoc2_sprintf_format(fmt, args) + "\\n");\n}\n\nfunction mtoc2_warning_fmt_id(_id, fmt, ...args) {\n // The id is informational \u2014 numbl threads it to lastwarn state,\n // which mtoc2 doesn\'t expose. Drop it here so the visible output\n // matches the bare form.\n $write("Warning: " + mtoc2_sprintf_format(fmt, args) + "\\n");\n}\n',
@@ -73496,7 +73749,7 @@ var JS_SNIPPETS = {
73496
73749
  "tensor_logspace.js": "// JS sibling of `tensor_logspace.h`. Build a 1\xD7n row tensor of n\n// logarithmically-spaced values from 10^a to 10^b. Byte-for-byte with\n// numbl's interpreter `logspace`, including the MATLAB special case\n// where an upper limit of exactly `pi` makes the last point `pi`.\n\n\nfunction mtoc2_tensor_logspace(a, b, n) {\n if (n <= 0) return mtoc2_tensor_alloc(1, 0);\n const isPi = b === Math.PI;\n const endVal = isPi ? Math.PI : Math.pow(10, b);\n const out = mtoc2_tensor_alloc(1, n);\n if (n === 1) {\n out.data[0] = endVal;\n return out;\n }\n if (isPi) {\n const logStart = Math.log10(Math.pow(10, a));\n const logEnd = Math.log10(Math.PI);\n for (let i = 0; i < n; i++) {\n const t = logStart + ((logEnd - logStart) * i) / (n - 1);\n out.data[i] = Math.pow(10, t);\n }\n } else {\n for (let i = 0; i < n; i++) {\n const t = a + ((b - a) * i) / (n - 1);\n out.data[i] = Math.pow(10, t);\n }\n }\n return out;\n}\n",
73497
73750
  "tensor_meshgrid.js": "// JS sibling of `tensor_meshgrid.h`. MATLAB-style coordinate grid.\n\n\nfunction numel(t) {\n let n = 1;\n for (const s of t.shape) n *= s;\n return n;\n}\n\nfunction mtoc2_meshgrid_x(x, y) {\n const M = numel(x);\n const N = numel(y);\n const X = mtoc2_tensor_alloc(N, M);\n for (let j = 0; j < M; j++) {\n const xj = x.data[j];\n for (let i = 0; i < N; i++) X.data[i + j * N] = xj;\n }\n return X;\n}\n\nfunction mtoc2_meshgrid(x, y) {\n const M = numel(x);\n const N = numel(y);\n const X = mtoc2_tensor_alloc(N, M);\n const Y = mtoc2_tensor_alloc(N, M);\n for (let j = 0; j < M; j++) {\n const xj = x.data[j];\n for (let i = 0; i < N; i++) {\n X.data[i + j * N] = xj;\n Y.data[i + j * N] = y.data[i];\n }\n }\n return { X, Y };\n}\n\nfunction mtoc2_meshgrid_1arg(x) {\n return mtoc2_meshgrid(x, x);\n}\n",
73498
73751
  "tensor_mtimes_complex.js": '// JS sibling of `tensor_mtimes_complex.h`. 2-D matrix multiplication\n// producing a complex result. Handles all three mixed cases\n// (complex \xD7 complex, complex \xD7 real, real \xD7 complex) by treating an\n// undefined imag lane as implicit zeros.\n\n\nfunction mtoc2_tensor_mtimes_complex_scalar(a, b) {\n if (a.shape.length !== 2 || b.shape.length !== 2) {\n throw new Error("mtoc2_tensor_mtimes_complex_scalar: inputs must be 2-D");\n }\n if (a.shape[0] !== 1 || b.shape[1] !== 1) {\n throw new Error("mtoc2_tensor_mtimes_complex_scalar: requires 1\xD7k * k\xD71");\n }\n if (a.shape[1] !== b.shape[0]) {\n throw new Error("mtoc2_tensor_mtimes_complex_scalar: inner-dim mismatch");\n }\n const k = a.shape[1];\n const aim = a.imag;\n const bim = b.imag;\n let accR = 0;\n let accI = 0;\n for (let p = 0; p < k; p++) {\n const ar = a.data[p];\n const ai = aim !== undefined ? aim[p] : 0;\n const br = b.data[p];\n const bi = bim !== undefined ? bim[p] : 0;\n accR += ar * br - ai * bi;\n accI += ar * bi + ai * br;\n }\n return { re: accR, im: accI };\n}\n\nfunction mtoc2_tensor_mtimes_complex(a, b) {\n if (a.shape.length !== 2 || b.shape.length !== 2) {\n throw new Error("mtoc2_tensor_mtimes_complex: inputs must be 2-D");\n }\n const m = a.shape[0];\n const k = a.shape[1];\n const k2 = b.shape[0];\n const n = b.shape[1];\n if (k !== k2) {\n throw new Error(\n `mtoc2_tensor_mtimes_complex: inner-dim mismatch (${m}\xD7${k} * ${k2}\xD7${n})`\n );\n }\n const out = mtoc2_tensor_alloc_complex(m, n);\n const aim = a.imag;\n const bim = b.imag;\n for (let j = 0; j < n; j++) {\n for (let i = 0; i < m; i++) {\n let accR = 0;\n let accI = 0;\n for (let p = 0; p < k; p++) {\n const ar = a.data[i + p * m];\n const ai = aim !== undefined ? aim[i + p * m] : 0;\n const br = b.data[p + j * k];\n const bi = bim !== undefined ? bim[p + j * k] : 0;\n accR += ar * br - ai * bi;\n accI += ar * bi + ai * br;\n }\n out.data[i + j * m] = accR;\n out.imag[i + j * m] = accI;\n }\n }\n return out;\n}\n',
73499
- "tensor_mtimes_real.js": '// JS sibling of `tensor_mtimes_real.h`. Real 2-D matrix\n// multiplication A * B. Column-major in, column-major out.\n// `mtoc2_tensor_mtimes_real_scalar` mirrors the C 1\xD7k * k\xD71\n// inner-product path that returns a bare number instead of a\n// freshly-allocated 1\xD71 tensor.\n\n\nfunction mtoc2_tensor_mtimes_real(a, b) {\n if (a.shape.length !== 2 || b.shape.length !== 2) {\n throw new Error("mtoc2_tensor_mtimes_real: inputs must be 2-D");\n }\n const m = a.shape[0];\n const k = a.shape[1];\n const k2 = b.shape[0];\n const n = b.shape[1];\n if (k !== k2) {\n throw new Error(\n `mtoc2_tensor_mtimes_real: inner dimensions disagree (${k} vs ${k2})`\n );\n }\n const r = mtoc2_tensor_alloc(m, n);\n for (let j = 0; j < n; j++) {\n for (let i = 0; i < m; i++) {\n let s = 0;\n for (let p = 0; p < k; p++) {\n s += a.data[i + p * m] * b.data[p + j * k];\n }\n r.data[i + j * m] = s;\n }\n }\n return r;\n}\n\nfunction mtoc2_tensor_mtimes_real_scalar(a, b) {\n if (a.shape.length !== 2 || b.shape.length !== 2) {\n throw new Error("mtoc2_tensor_mtimes_real_scalar: inputs must be 2-D");\n }\n // The translate-time type system has already proven a is 1\xD7k and b\n // is k\xD71; this helper trusts that and just runs the inner product.\n const k = a.shape[1];\n let s = 0;\n for (let p = 0; p < k; p++) {\n s += a.data[p * a.shape[0]] * b.data[p];\n }\n return s;\n}\n',
73752
+ "tensor_mtimes_real.js": '// JS sibling of `tensor_mtimes_real.h`. Real 2-D matrix\n// multiplication A * B. Column-major in, column-major out.\n// `mtoc2_tensor_mtimes_real_scalar` mirrors the C 1\xD7k * k\xD71\n// inner-product path that returns a bare number instead of a\n// freshly-allocated 1\xD71 tensor.\n\n\nfunction mtoc2_tensor_mtimes_real(a, b) {\n if (a.shape.length !== 2 || b.shape.length !== 2) {\n throw new Error("mtoc2_tensor_mtimes_real: inputs must be 2-D");\n }\n const m = a.shape[0];\n const k = a.shape[1];\n const k2 = b.shape[0];\n const n = b.shape[1];\n if (k !== k2) {\n throw new Error(\n `mtoc2_tensor_mtimes_real: inner dimensions disagree (${k} vs ${k2})`\n );\n }\n // Optional WASM LAPACK accelerator. When a host installs `$matmulAccel`\n // (browser worker, after loading a bridge), delegate the multiply; a\n // falsy return means "declined" (too small / error) and we fall through\n // to the JS loop below. `typeof` guard is safe even when unset.\n if (typeof $matmulAccel === "function") {\n const accel = $matmulAccel(a.data, m, k, b.data, n);\n if (accel) {\n const acc = mtoc2_tensor_alloc(m, n);\n acc.data.set(accel);\n return acc;\n }\n }\n const r = mtoc2_tensor_alloc(m, n);\n for (let j = 0; j < n; j++) {\n for (let i = 0; i < m; i++) {\n let s = 0;\n for (let p = 0; p < k; p++) {\n s += a.data[i + p * m] * b.data[p + j * k];\n }\n r.data[i + j * m] = s;\n }\n }\n return r;\n}\n\nfunction mtoc2_tensor_mtimes_real_scalar(a, b) {\n if (a.shape.length !== 2 || b.shape.length !== 2) {\n throw new Error("mtoc2_tensor_mtimes_real_scalar: inputs must be 2-D");\n }\n // The translate-time type system has already proven a is 1\xD7k and b\n // is k\xD71; this helper trusts that and just runs the inner product.\n const k = a.shape[1];\n let s = 0;\n for (let p = 0; p < k; p++) {\n s += a.data[p * a.shape[0]] * b.data[p];\n }\n return s;\n}\n',
73500
73753
  "tensor_norm.js": "// JS sibling of `tensor_norm.h`. Vector norms for real and complex\n// tensors. The complex variants tolerate a missing imag lane (real\n// tensor flowing through a complex-typed norm route) by treating it\n// as zero.\n\nfunction mtoc2_norm2_real(a) {\n let acc = 0;\n for (let i = 0; i < a.data.length; i++) {\n const x = a.data[i];\n acc += x * x;\n }\n return Math.sqrt(acc);\n}\n\nfunction mtoc2_norm2_complex(a) {\n let acc = 0;\n const im = a.imag;\n for (let i = 0; i < a.data.length; i++) {\n const re = a.data[i];\n const imv = im !== undefined ? im[i] : 0;\n acc += re * re + imv * imv;\n }\n return Math.sqrt(acc);\n}\n\nfunction mtoc2_norm_p_real(a, p) {\n const n = a.data.length;\n if (n === 0) return 0;\n if (p === Infinity) {\n let m = 0;\n for (let i = 0; i < n; i++) {\n const x = Math.abs(a.data[i]);\n if (x > m) m = x;\n }\n return m;\n }\n if (p === -Infinity) {\n let m = Math.abs(a.data[0]);\n for (let i = 1; i < n; i++) {\n const x = Math.abs(a.data[i]);\n if (x < m) m = x;\n }\n return m;\n }\n if (p === 1) {\n let acc = 0;\n for (let i = 0; i < n; i++) acc += Math.abs(a.data[i]);\n return acc;\n }\n if (p === 2) return mtoc2_norm2_real(a);\n let acc = 0;\n for (let i = 0; i < n; i++) acc += Math.pow(Math.abs(a.data[i]), p);\n return Math.pow(acc, 1 / p);\n}\n\nfunction mtoc2_norm_p_complex(a, p) {\n const n = a.data.length;\n if (n === 0) return 0;\n const im = a.imag;\n const abs = i => {\n const re = a.data[i];\n const imv = im !== undefined ? im[i] : 0;\n return Math.hypot(re, imv);\n };\n if (p === Infinity) {\n let m = 0;\n for (let i = 0; i < n; i++) {\n const x = abs(i);\n if (x > m) m = x;\n }\n return m;\n }\n if (p === -Infinity) {\n let m = abs(0);\n for (let i = 1; i < n; i++) {\n const x = abs(i);\n if (x < m) m = x;\n }\n return m;\n }\n if (p === 2) return mtoc2_norm2_complex(a);\n if (p === 1) {\n let acc = 0;\n for (let i = 0; i < n; i++) acc += abs(i);\n return acc;\n }\n let acc = 0;\n for (let i = 0; i < n; i++) acc += Math.pow(abs(i), p);\n return Math.pow(acc, 1 / p);\n}\n",
73501
73754
  "tensor_ones_nd.js": "// JS sibling of `tensor_ones_nd.h`. Fill the freshly-allocated\n// tensor with `1.0`.\n\n\nfunction mtoc2_tensor_ones_nd(ndim, dims) {\n const t = mtoc2_tensor_alloc_nd(ndim, dims);\n t.data.fill(1);\n return t;\n}\n",
73502
73755
  "tensor_ones_square.js": "// JS sibling of `tensor_ones_square.h`. See `tensor_zeros_square.js`\n// for the rationale.\n\n\nfunction mtoc2_tensor_ones_square(n) {\n return mtoc2_tensor_ones_nd(2, [n, n]);\n}\n",
@@ -82065,9 +82318,9 @@ function tileExact(src, padShape2, padReps) {
82065
82318
  }
82066
82319
  return out;
82067
82320
  }
82068
- function padTo(arr, pad, len) {
82321
+ function padTo(arr, pad2, len) {
82069
82322
  const out = arr.slice();
82070
- while (out.length < len) out.push(pad);
82323
+ while (out.length < len) out.push(pad2);
82071
82324
  return out;
82072
82325
  }
82073
82326
  function trimTrailingOnes(shape) {
@@ -93152,7 +93405,7 @@ var VirtualFileSystem = class {
93152
93405
  };
93153
93406
 
93154
93407
  // src/vfs/BrowserFileIOAdapter.ts
93155
- import { unzipSync } from "fflate";
93408
+ import { unzipSync, zipSync } from "fflate";
93156
93409
  var TEXT_DECODER = new TextDecoder("utf-8");
93157
93410
  var TEXT_ENCODER = new TextEncoder();
93158
93411
  var READ_CHUNK_SIZE = 8192;
@@ -93470,6 +93723,57 @@ var BrowserFileIOAdapter = class {
93470
93723
  }
93471
93724
  return extracted;
93472
93725
  }
93726
+ zip(zipfilename, filenames, rootDir) {
93727
+ const root = this.vfs.normalizePath(rootDir);
93728
+ const dest = this.vfs.normalizePath(zipfilename);
93729
+ const entries = {};
93730
+ const names = [];
93731
+ const toEntryName = (abs2) => {
93732
+ const prefix = root === "/" ? "/" : root + "/";
93733
+ if (!abs2.startsWith(prefix)) return abs2.slice(abs2.lastIndexOf("/") + 1);
93734
+ return abs2.slice(prefix.length);
93735
+ };
93736
+ const addFile = (abs2) => {
93737
+ const name = toEntryName(abs2);
93738
+ entries[name] = this.vfs.readFile(abs2);
93739
+ names.push(name);
93740
+ };
93741
+ const addDir = (absDir) => {
93742
+ for (const entry of this.vfs.listDir(absDir)) {
93743
+ if (entry.name === "." || entry.name === "..") continue;
93744
+ const abs2 = absDir + "/" + entry.name;
93745
+ if (entry.isdir) addDir(abs2);
93746
+ else addFile(abs2);
93747
+ }
93748
+ };
93749
+ const addPath = (abs2) => {
93750
+ const type = this.vfs.exists(abs2);
93751
+ if (type === null) throw new Error(`zip: cannot find '${abs2}'`);
93752
+ if (type === "dir") addDir(abs2);
93753
+ else addFile(abs2);
93754
+ };
93755
+ for (const f of filenames) {
93756
+ const p2 = f.replace(/\\/g, "/");
93757
+ const abs2 = p2.startsWith("/") ? this.vfs.normalizePath(p2) : this.vfs.normalizePath(root + "/" + p2);
93758
+ const last = p2.split("/").pop() ?? p2;
93759
+ if (last.includes("*") || last.includes("?")) {
93760
+ const lastSlash = abs2.lastIndexOf("/");
93761
+ const dir = lastSlash > 0 ? abs2.slice(0, lastSlash) : "/";
93762
+ const re = new RegExp(
93763
+ "^" + last.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".") + "$"
93764
+ );
93765
+ if (this.vfs.exists(dir) !== "dir") continue;
93766
+ for (const entry of this.vfs.listDir(dir)) {
93767
+ if (entry.name === "." || entry.name === "..") continue;
93768
+ if (re.test(entry.name)) addPath(dir + "/" + entry.name);
93769
+ }
93770
+ } else {
93771
+ addPath(abs2);
93772
+ }
93773
+ }
93774
+ this.vfs.writeFile(dest, zipSync(entries));
93775
+ return names;
93776
+ }
93473
93777
  /** Get the VFS changes for syncing back to the main thread. */
93474
93778
  getChanges() {
93475
93779
  for (const [, entry] of this.openFiles) {
@@ -93552,21 +93856,30 @@ var BrowserFileIOAdapter = class {
93552
93856
  );
93553
93857
  }
93554
93858
  listDirRecursive(pattern) {
93555
- const idx = pattern.indexOf("**");
93556
- let baseDir = pattern.slice(0, idx);
93859
+ const norm2 = pattern.replace(/\\/g, "/");
93860
+ const idx = norm2.indexOf("**");
93861
+ let baseDir = norm2.slice(0, idx);
93557
93862
  if (baseDir.endsWith("/")) baseDir = baseDir.slice(0, -1);
93558
93863
  if (!baseDir) baseDir = ".";
93864
+ const absBase = this.vfs.normalizePath(baseDir);
93865
+ if (this.vfs.exists(absBase) !== "dir") return [];
93866
+ const suffix = norm2.slice(idx);
93867
+ const DIRS = "\0";
93868
+ const ANY = "";
93869
+ const re = new RegExp(
93870
+ "^" + suffix.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*\//g, DIRS).replace(/\*\*/g, ANY).replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]").replace(new RegExp(DIRS, "g"), "(?:.*/)?").replace(new RegExp(ANY, "g"), ".*") + "$"
93871
+ );
93559
93872
  const results = [];
93560
- const walkDir = (dir) => {
93561
- const entries = this.vfs.listDir(dir);
93562
- results.push(...entries);
93563
- for (const entry of entries) {
93873
+ const walkDir = (dir, rel) => {
93874
+ const relPrefix = rel ? rel + "/" : "";
93875
+ for (const entry of this.vfs.listDir(dir)) {
93876
+ if (re.test(relPrefix + entry.name)) results.push(entry);
93564
93877
  if (entry.isdir && entry.name !== "." && entry.name !== "..") {
93565
- walkDir(entry.folder + "/" + entry.name);
93878
+ walkDir(entry.folder + "/" + entry.name, relPrefix + entry.name);
93566
93879
  }
93567
93880
  }
93568
93881
  };
93569
- walkDir(this.vfs.normalizePath(baseDir));
93882
+ walkDir(absBase, "");
93570
93883
  return results;
93571
93884
  }
93572
93885
  };