numbl 0.4.6 → 0.4.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.
Files changed (59) hide show
  1. package/dist-cli/cli.js +2600 -228
  2. package/dist-graphics/graphics/FigureView.d.ts +6 -0
  3. package/dist-graphics/graphics/SurfView.d.ts +18 -0
  4. package/dist-graphics/graphics/axisLimits.d.ts +23 -0
  5. package/dist-graphics/graphics/drawPlot.d.ts +2 -0
  6. package/dist-graphics/graphics/exportFigureHdf5.d.ts +21 -0
  7. package/dist-graphics/graphics/figureHashTransport.d.ts +24 -0
  8. package/dist-graphics/graphics/figureHdf5Schema.d.ts +19 -0
  9. package/dist-graphics/graphics/figureUpload.d.ts +45 -0
  10. package/dist-graphics/graphics/figuresReducer.d.ts +86 -0
  11. package/dist-graphics/graphics/importFigureHdf5.d.ts +9 -0
  12. package/dist-graphics/graphics/openInFigureViewer.d.ts +26 -0
  13. package/dist-graphics/graphics/plotHelpers.d.ts +6 -0
  14. package/dist-graphics/graphics/plotLegend.d.ts +2 -0
  15. package/dist-graphics/graphics/plotMarkers.d.ts +2 -0
  16. package/dist-graphics/graphics/restoreNaNs.d.ts +2 -0
  17. package/dist-graphics/graphics/surfColormap.d.ts +2 -0
  18. package/dist-graphics/graphics/types.d.ts +423 -0
  19. package/dist-graphics/graphics/uihtmlSrcDoc.d.ts +23 -0
  20. package/dist-graphics/graphics-lib.d.ts +21 -0
  21. package/dist-graphics/index.js +35334 -0
  22. package/dist-lib/lib.d.ts +4 -0
  23. package/dist-lib/lib.js +3286 -222
  24. package/dist-lib/numbl-core/executors/jit/hostHelpers.d.ts +4 -0
  25. package/dist-lib/numbl-core/fileIOAdapter.d.ts +13 -0
  26. package/dist-lib/numbl-core/interpreter/builtins/gallery.d.ts +7 -0
  27. package/dist-lib/numbl-core/interpreter/builtins/geometry.d.ts +3 -3
  28. package/dist-lib/numbl-core/interpreter/builtins/graph.d.ts +29 -0
  29. package/dist-lib/numbl-core/interpreter/builtins/index.d.ts +2 -0
  30. package/dist-lib/numbl-core/interpreter/interpreter.d.ts +5 -0
  31. package/dist-lib/numbl-core/interpreter/interpreterExec.d.ts +14 -1
  32. package/dist-lib/numbl-core/interpreter/interpreterFunctions.d.ts +6 -0
  33. package/dist-lib/numbl-core/interpreter/interpreterSpecialBuiltins.d.ts +4 -0
  34. package/dist-lib/numbl-core/interpreter/types.d.ts +13 -0
  35. package/dist-lib/numbl-core/jit/builtins/defs/math/rand.d.ts +2 -0
  36. package/dist-lib/numbl-core/jitDeclineDiagnostics.d.ts +32 -0
  37. package/dist-lib/numbl-core/jsUserFunctions.d.ts +13 -0
  38. package/dist-lib/numbl-core/lowering/classInfo.d.ts +19 -2
  39. package/dist-lib/numbl-core/native/geometry-bridge.d.ts +10 -0
  40. package/dist-lib/numbl-core/parser/ArgumentsParser.d.ts +6 -1
  41. package/dist-lib/numbl-core/runtime/constructors.d.ts +2 -1
  42. package/dist-lib/numbl-core/runtime/runtime.d.ts +2 -1
  43. package/dist-lib/numbl-core/runtime/runtimeMemberAccess.d.ts +1 -1
  44. package/dist-lib/numbl-core/version.d.ts +1 -1
  45. package/dist-lib/vfs/BrowserFileIOAdapter.d.ts +54 -0
  46. package/dist-lib/vfs/BrowserSystemAdapter.d.ts +27 -0
  47. package/dist-lib/vfs/VirtualFileSystem.d.ts +66 -0
  48. package/dist-plot-viewer/assets/hdf5_hl-C9YUKPMe.js +24296 -0
  49. package/dist-plot-viewer/assets/index-YeXWXIxH.js +4504 -0
  50. package/dist-plot-viewer/index.html +1 -1
  51. package/dist-site-viewer/assets/hdf5_hl-C9YUKPMe.js +24296 -0
  52. package/dist-site-viewer/assets/index-C-wfkZK0.js +4829 -0
  53. package/dist-site-viewer/assets/numbl-worker-Bnbz2rMQ.js +5228 -0
  54. package/dist-site-viewer/index.html +1 -1
  55. package/native/lapack_svd.cpp +50 -0
  56. package/package.json +21 -2
  57. package/dist-plot-viewer/assets/index-D4grNz8m.js +0 -4504
  58. package/dist-site-viewer/assets/index-B2mCFC55.js +0 -4826
  59. package/dist-site-viewer/assets/numbl-worker-DWZE29ck.js +0 -5116
package/dist-lib/lib.js CHANGED
@@ -1141,6 +1141,9 @@ var RTV = {
1141
1141
  }
1142
1142
  return new RuntimeClassInstance(className, fields, isHandleClass2);
1143
1143
  },
1144
+ classInstanceArray(className, elements, shape) {
1145
+ return new RuntimeClassInstanceArray(className, elements, shape);
1146
+ },
1144
1147
  complex(re, im) {
1145
1148
  return new RuntimeComplexNumber(re, im);
1146
1149
  },
@@ -18426,6 +18429,24 @@ function lu(data, m, n) {
18426
18429
  }
18427
18430
  function svd(data, m, n, econ, computeUV) {
18428
18431
  const k = Math.min(m, n);
18432
+ let finite = true;
18433
+ for (let i = 0; i < data.length; i++) {
18434
+ if (!Number.isFinite(data[i])) {
18435
+ finite = false;
18436
+ break;
18437
+ }
18438
+ }
18439
+ if (!finite) {
18440
+ const sNan = allocFloat64Array(k).fill(NaN);
18441
+ if (!computeUV) return { S: sNan };
18442
+ const uCols2 = econ ? k : m;
18443
+ const vCols2 = econ ? k : n;
18444
+ return {
18445
+ U: allocFloat64Array(m * uCols2).fill(NaN),
18446
+ S: sNan,
18447
+ V: allocFloat64Array(n * vCols2).fill(NaN)
18448
+ };
18449
+ }
18429
18450
  const a = allocFloat64Array(data);
18430
18451
  const s = allocFloat64Array(k);
18431
18452
  const JOBU_A2 = 0;
@@ -22299,6 +22320,24 @@ function sprintfFormat(fmt, args) {
22299
22320
  case "t":
22300
22321
  result += " ";
22301
22322
  break;
22323
+ case "r":
22324
+ result += "\r";
22325
+ break;
22326
+ case "a":
22327
+ result += "\x07";
22328
+ break;
22329
+ case "b":
22330
+ result += "\b";
22331
+ break;
22332
+ case "f":
22333
+ result += "\f";
22334
+ break;
22335
+ case "v":
22336
+ result += "\v";
22337
+ break;
22338
+ case "0":
22339
+ result += "\0";
22340
+ break;
22302
22341
  case "\\":
22303
22342
  result += "\\";
22304
22343
  break;
@@ -24235,8 +24274,8 @@ function indexIntoScalar(base, indices) {
24235
24274
  const idx = indices[0];
24236
24275
  if (isRuntimeTensor(idx)) {
24237
24276
  const is0x0 = idx.shape.length === 2 && idx.shape[0] === 0 && idx.shape[1] === 0;
24238
- const outShape = is0x0 ? [0, 0] : [...idx.shape];
24239
- return RTV.tensor(allocFloat64Array(0), outShape);
24277
+ const outShape2 = is0x0 ? [0, 0] : [...idx.shape];
24278
+ return RTV.tensor(allocFloat64Array(0), outShape2);
24240
24279
  }
24241
24280
  }
24242
24281
  if (indices.length === 1) {
@@ -24266,22 +24305,46 @@ function indexIntoScalar(base, indices) {
24266
24305
  if (k !== 1) throw new RuntimeError("Index exceeds array bounds");
24267
24306
  }
24268
24307
  const n = idx.data.length;
24269
- const scalarRe = isRuntimeNumber(base) ? base : base.re;
24270
- const data = allocFloat64Array(n);
24271
- data.fill(scalarRe);
24308
+ const scalarRe2 = isRuntimeNumber(base) ? base : base.re;
24309
+ const data2 = allocFloat64Array(n);
24310
+ data2.fill(scalarRe2);
24272
24311
  if (isRuntimeComplexNumber(base) && base.im !== 0) {
24273
24312
  const im = allocFloat64Array(n);
24274
24313
  im.fill(base.im);
24275
- return RTV.tensor(data, [...idx.shape], im);
24314
+ return RTV.tensor(data2, [...idx.shape], im);
24276
24315
  }
24277
- return RTV.tensor(data, [...idx.shape]);
24316
+ return RTV.tensor(data2, [...idx.shape]);
24278
24317
  }
24318
+ const dimLengths = [];
24319
+ let anyTensor = false;
24279
24320
  for (const idx of indices) {
24280
- if (isColonIndex(idx)) continue;
24281
- const i = toNumber(idx);
24282
- if (i !== 1) throw new RuntimeError("Index exceeds array bounds");
24321
+ if (isColonIndex(idx)) {
24322
+ dimLengths.push(1);
24323
+ } else if (isRuntimeTensor(idx)) {
24324
+ anyTensor = true;
24325
+ for (let i = 0; i < idx.data.length; i++) {
24326
+ if (Math.round(idx.data[i]) !== 1)
24327
+ throw new RuntimeError("Index exceeds array bounds");
24328
+ }
24329
+ dimLengths.push(idx.data.length);
24330
+ } else {
24331
+ if (Math.round(toNumber(idx)) !== 1)
24332
+ throw new RuntimeError("Index exceeds array bounds");
24333
+ dimLengths.push(1);
24334
+ }
24283
24335
  }
24284
- return base;
24336
+ if (!anyTensor) return base;
24337
+ const total = dimLengths.reduce((a, b) => a * b, 1);
24338
+ const scalarRe = isRuntimeNumber(base) ? base : base.re;
24339
+ const data = allocFloat64Array(total);
24340
+ data.fill(scalarRe);
24341
+ const outShape = dimLengths.length >= 2 ? dimLengths : [dimLengths[0], 1];
24342
+ if (isRuntimeComplexNumber(base) && base.im !== 0) {
24343
+ const im = allocFloat64Array(total);
24344
+ im.fill(base.im);
24345
+ return RTV.tensor(data, outShape, im);
24346
+ }
24347
+ return RTV.tensor(data, outShape);
24285
24348
  }
24286
24349
  function indexIntoTensor(base, indices) {
24287
24350
  if (indices.length === 1) {
@@ -24316,11 +24379,13 @@ function indexIntoTensor(base, indices) {
24316
24379
  function indexIntoTensor1D(base, idx) {
24317
24380
  if (isColonIndex(idx)) {
24318
24381
  const imag2 = base.imag ? allocFloat64Array(base.imag) : void 0;
24319
- return RTV.tensor(
24382
+ const result = RTV.tensor(
24320
24383
  allocFloat64Array(base.data),
24321
24384
  [base.data.length, 1],
24322
24385
  imag2
24323
24386
  );
24387
+ if (base._isLogical === true) result._isLogical = true;
24388
+ return result;
24324
24389
  }
24325
24390
  if (isRuntimeLogical(idx)) {
24326
24391
  if (!idx) return RTV.tensor(allocFloat64Array(0), [0, 0]);
@@ -24518,6 +24583,17 @@ function indexIntoChar(base, indices) {
24518
24583
  if (indices.length === 1) {
24519
24584
  const idx = indices[0];
24520
24585
  if (isColonIndex(idx)) return base;
24586
+ if (isRuntimeTensor(idx) && idx._isLogical) {
24587
+ let result = "";
24588
+ for (let k = 0; k < idx.data.length; k++) {
24589
+ if (idx.data[k] !== 0) {
24590
+ if (k >= base.value.length)
24591
+ throw new RuntimeError("Index exceeds char array length");
24592
+ result += base.value[k];
24593
+ }
24594
+ }
24595
+ return RTV.char(result);
24596
+ }
24521
24597
  if (isRuntimeTensor(idx)) {
24522
24598
  let result = "";
24523
24599
  for (let k = 0; k < idx.data.length; k++) {
@@ -24536,22 +24612,19 @@ function indexIntoChar(base, indices) {
24536
24612
  if (indices.length === 2) {
24537
24613
  const rowIdx = indices[0];
24538
24614
  const colIdx = indices[1];
24539
- let rows;
24540
- if (isColonIndex(rowIdx)) {
24541
- rows = Array.from({ length: nRows }, (_, i) => i);
24542
- } else if (isRuntimeTensor(rowIdx)) {
24543
- rows = Array.from(rowIdx.data, (v) => Math.round(v) - 1);
24544
- } else {
24545
- rows = [Math.round(toNumber(rowIdx)) - 1];
24546
- }
24547
- let cols;
24548
- if (isColonIndex(colIdx)) {
24549
- cols = Array.from({ length: nCols }, (_, i) => i);
24550
- } else if (isRuntimeTensor(colIdx)) {
24551
- cols = Array.from(colIdx.data, (v) => Math.round(v) - 1);
24552
- } else {
24553
- cols = [Math.round(toNumber(colIdx)) - 1];
24554
- }
24615
+ const toPositions = (idx) => {
24616
+ if (isRuntimeTensor(idx) && idx._isLogical) {
24617
+ const out = [];
24618
+ for (let i = 0; i < idx.data.length; i++)
24619
+ if (idx.data[i] !== 0) out.push(i);
24620
+ return out;
24621
+ }
24622
+ if (isRuntimeTensor(idx))
24623
+ return Array.from(idx.data, (v) => Math.round(v) - 1);
24624
+ return [Math.round(toNumber(idx)) - 1];
24625
+ };
24626
+ const rows = isColonIndex(rowIdx) ? Array.from({ length: nRows }, (_, i) => i) : toPositions(rowIdx);
24627
+ const cols = isColonIndex(colIdx) ? Array.from({ length: nCols }, (_, i) => i) : toPositions(colIdx);
24555
24628
  let result = "";
24556
24629
  for (const r of rows) {
24557
24630
  for (const c of cols) {
@@ -24577,11 +24650,11 @@ function indexIntoLogical(base, indices) {
24577
24650
  const vi = Math.round(idx.data[i2]);
24578
24651
  if (vi !== 1) throw new RuntimeError("Index exceeds array bounds");
24579
24652
  }
24580
- const data = allocFloat64Array(idx.data.length);
24581
- data.fill(base ? 1 : 0);
24582
- const result = RTV.tensor(data, [1, idx.data.length]);
24583
- result._isLogical = true;
24584
- return result;
24653
+ const data2 = allocFloat64Array(idx.data.length);
24654
+ data2.fill(base ? 1 : 0);
24655
+ const result2 = RTV.tensor(data2, [...idx.shape]);
24656
+ result2._isLogical = true;
24657
+ return result2;
24585
24658
  }
24586
24659
  if (isRuntimeLogical(idx)) {
24587
24660
  if (!idx) return RTV.tensor(allocFloat64Array(0), [0, 0]);
@@ -24591,16 +24664,35 @@ function indexIntoLogical(base, indices) {
24591
24664
  if (i !== 1) throw new RuntimeError("Index exceeds array bounds");
24592
24665
  return base;
24593
24666
  }
24667
+ const dimLengths = [];
24668
+ let anyTensor = false;
24594
24669
  for (const idx of indices) {
24595
- if (isColonIndex(idx)) continue;
24596
- if (isRuntimeLogical(idx)) {
24670
+ if (isColonIndex(idx)) {
24671
+ dimLengths.push(1);
24672
+ } else if (isRuntimeLogical(idx)) {
24597
24673
  if (!idx) return RTV.tensor(allocFloat64Array(0), [0, 0]);
24598
- continue;
24674
+ dimLengths.push(1);
24675
+ } else if (isRuntimeTensor(idx)) {
24676
+ anyTensor = true;
24677
+ for (let i = 0; i < idx.data.length; i++) {
24678
+ if (Math.round(idx.data[i]) !== 1)
24679
+ throw new RuntimeError("Index exceeds array bounds");
24680
+ }
24681
+ dimLengths.push(idx.data.length);
24682
+ } else {
24683
+ if (Math.round(toNumber(idx)) !== 1)
24684
+ throw new RuntimeError("Index exceeds array bounds");
24685
+ dimLengths.push(1);
24599
24686
  }
24600
- const i = Math.round(toNumber(idx));
24601
- if (i !== 1) throw new RuntimeError("Index exceeds array bounds");
24602
24687
  }
24603
- return base;
24688
+ if (!anyTensor) return base;
24689
+ const total = dimLengths.reduce((a, b) => a * b, 1);
24690
+ const data = allocFloat64Array(total);
24691
+ data.fill(base ? 1 : 0);
24692
+ const outShape = dimLengths.length >= 2 ? dimLengths : [dimLengths[0], 1];
24693
+ const result = RTV.tensor(data, outShape);
24694
+ result._isLogical = true;
24695
+ return result;
24604
24696
  }
24605
24697
  function indexIntoTensorWithTensor(base, idx) {
24606
24698
  const baseLogical = base._isLogical === true;
@@ -24680,11 +24772,15 @@ function indexIntoRTValue(base, indices) {
24680
24772
  return indexIntoLogical(base, indices);
24681
24773
  }
24682
24774
  if (isRuntimeStruct(base) || isRuntimeClassInstance(base)) {
24683
- if (indices.length === 1) {
24684
- const i = Math.round(toNumber(indices[0]));
24685
- if (i !== 1) throw new RuntimeError("Index exceeds struct dimensions");
24686
- return base;
24687
- }
24775
+ const allOnes = indices.length >= 1 && indices.every((idx) => {
24776
+ if (isRuntimeNumber(idx)) return Math.round(idx) === 1;
24777
+ if (isRuntimeLogical(idx)) return idx === true;
24778
+ if (isRuntimeTensor(idx))
24779
+ return idx.data.length === 1 && Math.round(idx.data[0]) === 1;
24780
+ return false;
24781
+ });
24782
+ if (allOnes) return base;
24783
+ throw new RuntimeError("Index exceeds struct dimensions");
24688
24784
  }
24689
24785
  if (isRuntimeSparseMatrix(base)) {
24690
24786
  return indexIntoSparse(base, indices);
@@ -24734,7 +24830,13 @@ function storeIntoTensor(base, indices, rhs, _rt) {
24734
24830
  }
24735
24831
  if (isShared(base)) {
24736
24832
  const cowImag = base.imag ? allocFloat64Array(base.imag) : void 0;
24737
- base = RTV.tensor(allocFloat64Array(base.data), [...base.shape], cowImag);
24833
+ const copy = RTV.tensor(
24834
+ allocFloat64Array(base.data),
24835
+ [...base.shape],
24836
+ cowImag
24837
+ );
24838
+ copy._isLogical = base._isLogical;
24839
+ base = copy;
24738
24840
  }
24739
24841
  if (indices.length === 1) {
24740
24842
  return storeIntoTensor1D(base, indices[0], rhs);
@@ -24762,7 +24864,9 @@ function deleteTensorElements(base, idx) {
24762
24864
  toDelete.add(Math.round(idx.data[i]) - 1);
24763
24865
  }
24764
24866
  } else if (isColonIndex(idx)) {
24765
- return RTV.tensor(allocFloat64Array(0), [0, 0]);
24867
+ const empty = RTV.tensor(allocFloat64Array(0), [0, 0]);
24868
+ empty._isLogical = base._isLogical;
24869
+ return empty;
24766
24870
  }
24767
24871
  if (toDelete.size === 0) return base;
24768
24872
  const newData = [];
@@ -24777,7 +24881,9 @@ function deleteTensorElements(base, idx) {
24777
24881
  const baseIsColVec = base.shape.length >= 2 && base.shape[1] === 1 && base.shape[0] !== 1;
24778
24882
  const outShape = baseIsColVec ? [newData.length, 1] : [1, newData.length];
24779
24883
  const imOut = hasImag && newIm.some((x) => x !== 0) ? allocFloat64Array(newIm) : void 0;
24780
- return RTV.tensor(allocFloat64Array(newData), outShape, imOut);
24884
+ const result = RTV.tensor(allocFloat64Array(newData), outShape, imOut);
24885
+ result._isLogical = base._isLogical;
24886
+ return result;
24781
24887
  }
24782
24888
  function collectDelIndices(idx, dimLen) {
24783
24889
  const s = /* @__PURE__ */ new Set();
@@ -24817,7 +24923,9 @@ function deleteTensorRowsOrCols(base, indices) {
24817
24923
  if (newIm && base.imag) newIm[dstIdx] = base.imag[srcIdx];
24818
24924
  }
24819
24925
  }
24820
- return RTV.tensor(newData, [newNrows, ncols], newIm);
24926
+ const result = RTV.tensor(newData, [newNrows, ncols], newIm);
24927
+ result._isLogical = base._isLogical;
24928
+ return result;
24821
24929
  }
24822
24930
  if (isColonIndex(indices[0])) {
24823
24931
  const delCols = collectDelIndices(indices[1], ncols);
@@ -24836,7 +24944,9 @@ function deleteTensorRowsOrCols(base, indices) {
24836
24944
  if (newIm && base.imag) newIm[dstIdx] = base.imag[srcIdx];
24837
24945
  }
24838
24946
  }
24839
- return RTV.tensor(newData, [nrows, newNcols], newIm);
24947
+ const result = RTV.tensor(newData, [nrows, newNcols], newIm);
24948
+ result._isLogical = base._isLogical;
24949
+ return result;
24840
24950
  }
24841
24951
  throw new RuntimeError("Cannot delete from both row and column dimensions");
24842
24952
  }
@@ -24906,6 +25016,9 @@ function storeIntoTensor1D(base, idx, rhs) {
24906
25016
  }
24907
25017
  function storeIntoTensorByVector(base, idx, rhs) {
24908
25018
  if (idx._isLogical) {
25019
+ if (isRuntimeTensor(rhs) && rhs.data.length === 1) {
25020
+ rhs = rhs.imag && rhs.imag[0] !== 0 ? RTV.complex(rhs.data[0], rhs.imag[0]) : RTV.num(rhs.data[0]);
25021
+ }
24909
25022
  const { re: rhsRe, im: rhsIm } = isRuntimeTensor(rhs) ? { re: null, im: null } : toReIm(rhs);
24910
25023
  let maxTruthy = -1;
24911
25024
  for (let i = 0; i < idx.data.length; i++) {
@@ -25155,7 +25268,8 @@ function storeIntoTensorND(base, indices, rhs) {
25155
25268
  const dimIndices = indices.map((idx, dim) => {
25156
25269
  const dimSize = dim < shape.length ? shape[dim] : 1;
25157
25270
  if (isColonIndex(idx)) {
25158
- if (dimSize === 0 && rhsShape) {
25271
+ const dimSizeUnknown = dimSize === 0 || dim >= shape.length;
25272
+ if (dimSizeUnknown && rhsShape) {
25159
25273
  const rDim = rhsDimCursor < rhsShape.length ? rhsShape[rhsDimCursor] : 1;
25160
25274
  rhsDimCursor++;
25161
25275
  return Array.from({ length: rDim }, (_, i) => i);
@@ -25472,6 +25586,9 @@ function horzcat(...values) {
25472
25586
  if (values.some((v) => isRuntimeSparseMatrix(v))) {
25473
25587
  return sparseCatAlongDim(values, 1);
25474
25588
  }
25589
+ if (values.some((v) => isRuntimeCell(v))) {
25590
+ return cellCatAlongDim(values, 1);
25591
+ }
25475
25592
  if (values.some((v) => isRuntimeChar(v))) {
25476
25593
  let result = "";
25477
25594
  for (const v of values) {
@@ -25492,9 +25609,6 @@ function horzcat(...values) {
25492
25609
  }
25493
25610
  return RTV.string(result);
25494
25611
  }
25495
- if (values.some((v) => isRuntimeCell(v))) {
25496
- return cellCatAlongDim(values, 1);
25497
- }
25498
25612
  if (values.some((v) => isRuntimeStruct(v) || isRuntimeStructArray(v))) {
25499
25613
  return structCat(values);
25500
25614
  }
@@ -25506,12 +25620,12 @@ function vertcat(...values) {
25506
25620
  if (values.some((v) => isRuntimeSparseMatrix(v))) {
25507
25621
  return sparseCatAlongDim(values, 0);
25508
25622
  }
25509
- if (values.some((v) => isRuntimeChar(v))) {
25510
- return vertcatChars(values);
25511
- }
25512
25623
  if (values.some((v) => isRuntimeCell(v))) {
25513
25624
  return cellCatAlongDim(values, 0);
25514
25625
  }
25626
+ if (values.some((v) => isRuntimeChar(v))) {
25627
+ return vertcatChars(values);
25628
+ }
25515
25629
  if (values.some((v) => isRuntimeStruct(v) || isRuntimeStructArray(v))) {
25516
25630
  return structCat(values);
25517
25631
  }
@@ -25857,11 +25971,22 @@ function catAlongDim(values, dimIdx) {
25857
25971
  if (allLogical) result._isLogical = true;
25858
25972
  return result;
25859
25973
  }
25974
+ function isEmptyNonCellOperand(v) {
25975
+ if (isRuntimeChar(v)) return v.value.length === 0;
25976
+ if (isRuntimeTensor(v)) return v.data.length === 0;
25977
+ return false;
25978
+ }
25860
25979
  function cellCatAlongDim(values, dimIdx) {
25861
- let cells = values.map((v) => {
25862
- if (isRuntimeCell(v)) return v;
25863
- return RTV.cell([v], [1, 1]);
25864
- });
25980
+ let cells = [];
25981
+ for (const v of values) {
25982
+ if (isRuntimeCell(v)) {
25983
+ cells.push(v);
25984
+ } else if (isEmptyNonCellOperand(v)) {
25985
+ continue;
25986
+ } else {
25987
+ cells.push(RTV.cell([v], [1, 1]));
25988
+ }
25989
+ }
25865
25990
  cells = cells.filter((c) => c.data.length > 0);
25866
25991
  if (cells.length === 0) return RTV.cell([], [0, 0]);
25867
25992
  if (cells.length === 1) return cells[0];
@@ -29760,7 +29885,7 @@ var ExpressionParser = class extends ParserBase {
29760
29885
  }
29761
29886
  } else if (expr.type === "Ident" && this.peekToken() === 41 /* At */ && this.peekTokenAt(1) === 4 /* Ident */) {
29762
29887
  this.pos++;
29763
- const className = this.expectIdent();
29888
+ const className = this.parseQualifiedName();
29764
29889
  if (!this.consume(50 /* LParen */)) {
29765
29890
  throw this.error("expected '(' after superclass name in super call");
29766
29891
  }
@@ -30361,7 +30486,10 @@ var ControlFlowParser = class extends CommandParser {
30361
30486
  let catchBody = [];
30362
30487
  if (this.consume(27 /* Catch */)) {
30363
30488
  if (this.peekToken() === 4 /* Ident */) {
30364
- catchVar = this.expectIdent();
30489
+ const after = this.peekTokenAt(1);
30490
+ if (after === void 0 || after === 68 /* Newline */ || after === 49 /* Semicolon */ || after === 47 /* Comma */ || after === 15 /* End */) {
30491
+ catchVar = this.expectIdent();
30492
+ }
30365
30493
  }
30366
30494
  catchBody = this.parseBlock((t) => t === 15 /* End */);
30367
30495
  }
@@ -30486,8 +30614,12 @@ var ArgumentsParser = class extends ControlFlowParser {
30486
30614
  dimensions = this.parseArgDimensions();
30487
30615
  }
30488
30616
  let className = null;
30489
- if (this.peekToken() === 4 /* Ident */ && this.peekToken() !== 68 /* Newline */ && this.peekToken() !== 49 /* Semicolon */) {
30617
+ if (this.peekToken() === 4 /* Ident */) {
30490
30618
  className = this.next().lexeme;
30619
+ while (this.peekToken() === 45 /* Dot */) {
30620
+ this.consume(45 /* Dot */);
30621
+ className += "." + this.expectIdent();
30622
+ }
30491
30623
  }
30492
30624
  let validators = [];
30493
30625
  if (this.peekToken() === 54 /* LBrace */) {
@@ -30523,18 +30655,28 @@ var ArgumentsParser = class extends ControlFlowParser {
30523
30655
  return dims;
30524
30656
  }
30525
30657
  /**
30526
- * Parse validator list: {mustBeNumeric, mustBePositive}
30658
+ * Parse validator list: {mustBeNumeric, mustBePositive}. Validators may be
30659
+ * full function calls with arguments, e.g.
30660
+ * `{mustBeMember(x,["a","b"]), mustBeInRange(x,0,7)}`. The validator bodies
30661
+ * are not enforced at runtime, so we capture the top-level validator name
30662
+ * tokens and skip past any nested `(...)`, `[...]`, `{...}` so parsing
30663
+ * resumes correctly at the default value (`= expr`) or end of line.
30527
30664
  */
30528
30665
  parseArgValidators() {
30529
30666
  this.consume(54 /* LBrace */);
30530
30667
  const validators = [];
30531
- while (this.peekToken() !== 55 /* RBrace */ && this.peekToken() !== void 0) {
30532
- if (this.consume(47 /* Comma */)) continue;
30533
- if (this.peekToken() === 4 /* Ident */) {
30534
- validators.push(this.next().lexeme);
30535
- } else {
30536
- break;
30668
+ let depth = 0;
30669
+ while (this.peekToken() !== void 0) {
30670
+ const tok = this.peekToken();
30671
+ if (depth === 0 && tok === 55 /* RBrace */) break;
30672
+ if (tok === 50 /* LParen */ || tok === 52 /* LBracket */ || tok === 54 /* LBrace */) {
30673
+ depth++;
30674
+ } else if (tok === 51 /* RParen */ || tok === 53 /* RBracket */ || tok === 55 /* RBrace */) {
30675
+ depth--;
30676
+ } else if (depth === 0 && tok === 4 /* Ident */) {
30677
+ validators.push(this.peek().lexeme);
30537
30678
  }
30679
+ this.next();
30538
30680
  }
30539
30681
  this.consume(55 /* RBrace */);
30540
30682
  return validators;
@@ -30565,7 +30707,8 @@ var FunctionParser = class extends ArgumentsParser {
30565
30707
  if (this.consume(52 /* LBracket */)) {
30566
30708
  if (this.peekToken() !== 53 /* RBracket */) {
30567
30709
  outputs.push(this.expectIdentOrTilde());
30568
- while (this.consume(47 /* Comma */)) {
30710
+ while (this.peekToken() === 47 /* Comma */ || this.peekToken() === 4 /* Ident */ || this.peekToken() === 40 /* Tilde */) {
30711
+ this.consume(47 /* Comma */);
30569
30712
  outputs.push(this.expectIdentOrTilde());
30570
30713
  }
30571
30714
  }
@@ -30673,6 +30816,14 @@ var FunctionParser = class extends ArgumentsParser {
30673
30816
  };
30674
30817
 
30675
30818
  // src/numbl-core/parser/ClassParser.ts
30819
+ var HANDLE_BASE_CLASSES = /* @__PURE__ */ new Set([
30820
+ "handle",
30821
+ "dynamicprops",
30822
+ "matlab.mixin.Copyable",
30823
+ "matlab.mixin.SetGet",
30824
+ "matlab.mixin.SetGetExactNames",
30825
+ "hgsetget"
30826
+ ]);
30676
30827
  var ClassParser = class extends FunctionParser {
30677
30828
  // ── Imports & ClassDef ───────────────────────────────────────────────
30678
30829
  parseImport() {
@@ -30707,7 +30858,11 @@ var ClassParser = class extends FunctionParser {
30707
30858
  const name = this.parseQualifiedName();
30708
30859
  let superClass = null;
30709
30860
  if (this.consume(43 /* Less */)) {
30710
- superClass = this.parseQualifiedName();
30861
+ const supers = [this.parseQualifiedName()];
30862
+ while (this.consume(38 /* And */)) {
30863
+ supers.push(this.parseQualifiedName());
30864
+ }
30865
+ superClass = supers.some((s) => HANDLE_BASE_CLASSES.has(s)) ? "handle" : supers[0];
30711
30866
  }
30712
30867
  const members = [];
30713
30868
  while (true) {
@@ -31582,7 +31737,7 @@ function insertFunctionEndTokens(tokens) {
31582
31737
  }
31583
31738
  if (BLOCK_OPENERS.has(tok.token)) {
31584
31739
  depth++;
31585
- } else if (tok.token === 15 /* End */ && groupDepth === 0) {
31740
+ } else if (tok.token === 15 /* End */ && groupDepth === 0 && tokens[i - 1]?.token !== 45 /* Dot */) {
31586
31741
  depth--;
31587
31742
  if (depth === 0 && inFunction) {
31588
31743
  inFunction = false;
@@ -34294,6 +34449,8 @@ defineBuiltin({
34294
34449
  if (isRuntimeComplexNumber(v)) return v.im === 0;
34295
34450
  if (isRuntimeTensor(v)) return imagAllZero(v.imag);
34296
34451
  if (isRuntimeSparseMatrix(v)) return !v.pi || imagAllZero(v.pi);
34452
+ if (isRuntimeCell(v) || isRuntimeStruct(v) || isRuntimeStructArray(v) || isRuntimeString(v) || isRuntimeFunction(v))
34453
+ return false;
34297
34454
  return true;
34298
34455
  }
34299
34456
  }
@@ -34765,10 +34922,79 @@ defineBuiltin({
34765
34922
  name: "ischar",
34766
34923
  cases: [anyToLogicalCase((args) => isRuntimeChar(args[0]))]
34767
34924
  });
34925
+ defineBuiltin({
34926
+ name: "isstr",
34927
+ cases: [anyToLogicalCase((args) => isRuntimeChar(args[0]))]
34928
+ });
34768
34929
  defineBuiltin({
34769
34930
  name: "isstring",
34770
34931
  cases: [anyToLogicalCase((args) => isRuntimeString(args[0]))]
34771
34932
  });
34933
+ var MATLAB_KEYWORDS = /* @__PURE__ */ new Set([
34934
+ "break",
34935
+ "case",
34936
+ "catch",
34937
+ "classdef",
34938
+ "continue",
34939
+ "else",
34940
+ "elseif",
34941
+ "end",
34942
+ "for",
34943
+ "function",
34944
+ "global",
34945
+ "if",
34946
+ "otherwise",
34947
+ "parfor",
34948
+ "persistent",
34949
+ "return",
34950
+ "spmd",
34951
+ "switch",
34952
+ "try",
34953
+ "while"
34954
+ ]);
34955
+ function singleRowText(v) {
34956
+ if (isRuntimeChar(v)) {
34957
+ if (v.shape && v.shape.length >= 1 && v.shape[0] > 1) return null;
34958
+ return v.value;
34959
+ }
34960
+ if (isRuntimeString(v)) return v;
34961
+ return null;
34962
+ }
34963
+ defineBuiltin({
34964
+ name: "isvarname",
34965
+ cases: [
34966
+ anyToLogicalCase((args) => {
34967
+ const s = singleRowText(args[0]);
34968
+ if (s === null || s.length === 0 || s.length > 63) return false;
34969
+ if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(s)) return false;
34970
+ return !MATLAB_KEYWORDS.has(s);
34971
+ })
34972
+ ]
34973
+ });
34974
+ defineBuiltin({
34975
+ name: "iskeyword",
34976
+ cases: [
34977
+ {
34978
+ // iskeyword() with no args returns the list of keywords (column cell).
34979
+ match: (argTypes) => {
34980
+ if (argTypes.length === 0) return [{ kind: "cell" }];
34981
+ if (argTypes.length === 1) return [{ kind: "boolean" }];
34982
+ return null;
34983
+ },
34984
+ apply: (args) => {
34985
+ if (args.length === 0) {
34986
+ const names = [...MATLAB_KEYWORDS].sort();
34987
+ return RTV.cell(
34988
+ names.map((n) => RTV.char(n)),
34989
+ [names.length, 1]
34990
+ );
34991
+ }
34992
+ const s = singleRowText(args[0]);
34993
+ return RTV.logical(s !== null && MATLAB_KEYWORDS.has(s));
34994
+ }
34995
+ }
34996
+ ]
34997
+ });
34772
34998
  defineBuiltin({
34773
34999
  name: "iscell",
34774
35000
  cases: [anyToLogicalCase((args) => isRuntimeCell(args[0]))]
@@ -34785,6 +35011,82 @@ defineBuiltin({
34785
35011
  name: "issparse",
34786
35012
  cases: [anyToLogicalCase((args) => isRuntimeSparseMatrix(args[0]))]
34787
35013
  });
35014
+ defineBuiltin({
35015
+ name: "isobject",
35016
+ cases: [
35017
+ anyToLogicalCase(
35018
+ (args) => isRuntimeClassInstance(args[0]) || isRuntimeClassInstanceArray(args[0])
35019
+ )
35020
+ ]
35021
+ });
35022
+ defineBuiltin({
35023
+ name: "isprop",
35024
+ help: {
35025
+ signatures: ["tf = isprop(obj, PropertyName)"],
35026
+ description: "Return logical 1 where PropertyName is a property of object obj, else 0. The result has the same size as obj. Only class objects have properties: structs (even with that field), numeric, char and other built-in types always return false. Methods are not properties."
35027
+ },
35028
+ cases: [
35029
+ {
35030
+ match: (argTypes) => {
35031
+ if (argTypes.length !== 2) return null;
35032
+ return [{ kind: "boolean" }];
35033
+ },
35034
+ apply: (args) => {
35035
+ const v = args[0];
35036
+ const nameArg = args[1];
35037
+ let propName = null;
35038
+ if (isRuntimeChar(nameArg)) propName = nameArg.value;
35039
+ else if (isRuntimeString(nameArg)) propName = nameArg;
35040
+ let answer = false;
35041
+ if (propName !== null) {
35042
+ if (isRuntimeClassInstance(v)) answer = v.fields.has(propName);
35043
+ else if (isRuntimeClassInstanceArray(v))
35044
+ answer = v.elements.length > 0 && v.elements[0].fields.has(propName);
35045
+ }
35046
+ const shape = getShape(v);
35047
+ const n = shape.reduce((a, b) => a * b, 1);
35048
+ if (n === 1) return RTV.logical(answer);
35049
+ const data = allocFloat64Array(n);
35050
+ if (answer) data.fill(1);
35051
+ return new RuntimeTensor(data, shape, void 0, true);
35052
+ }
35053
+ }
35054
+ ]
35055
+ });
35056
+ defineBuiltin({
35057
+ name: "addprop",
35058
+ help: {
35059
+ signatures: ["p = addprop(obj, PropertyName)"],
35060
+ description: "Add a dynamic property named PropertyName to the dynamicprops (handle) object obj. The property can then be read and assigned via obj.PropertyName. Returns a meta.DynamicProperty describing the new property."
35061
+ },
35062
+ cases: [
35063
+ {
35064
+ match: (argTypes) => {
35065
+ if (argTypes.length !== 2) return null;
35066
+ return [{ kind: "unknown" }];
35067
+ },
35068
+ apply: (args) => {
35069
+ const obj = args[0];
35070
+ if (!isRuntimeClassInstance(obj))
35071
+ throw new RuntimeError(
35072
+ "addprop: first argument must be a dynamicprops object"
35073
+ );
35074
+ const name = toString(args[1]);
35075
+ if (!obj.fields.has(name)) {
35076
+ const empty = RTV.tensor(allocFloat64Array(0), [0, 0]);
35077
+ incref(empty);
35078
+ obj.fields.set(name, empty);
35079
+ }
35080
+ return RTV.classInstance(
35081
+ "meta.DynamicProperty",
35082
+ ["Name"],
35083
+ true,
35084
+ /* @__PURE__ */ new Map([["Name", RTV.char(name)]])
35085
+ );
35086
+ }
35087
+ }
35088
+ ]
35089
+ });
34788
35090
  defineBuiltin({
34789
35091
  name: "isscalar",
34790
35092
  cases: [
@@ -34982,6 +35284,30 @@ function mkChar(value) {
34982
35284
  defineBuiltin({
34983
35285
  name: "class",
34984
35286
  cases: [
35287
+ // Old-style (pre-classdef) constructor form: class(structData, 'ClassName')
35288
+ // builds a value-type instance whose fields are the struct's fields. The
35289
+ // optional class(s,'Name',parent,...) inheritance form is not supported
35290
+ // (returns null → "unsupported argument types").
35291
+ {
35292
+ match: (argTypes) => {
35293
+ if (argTypes.length !== 2) return null;
35294
+ if (argTypes[0].kind !== "struct") return null;
35295
+ const k = argTypes[1].kind;
35296
+ if (k !== "char" && k !== "string") return null;
35297
+ return [{ kind: "unknown" }];
35298
+ },
35299
+ apply: (args) => {
35300
+ const s = args[0];
35301
+ if (!isRuntimeStruct(s))
35302
+ throw new RuntimeError(
35303
+ "class: first argument must be a scalar struct"
35304
+ );
35305
+ const nameVal = args[1];
35306
+ const className = isRuntimeChar(nameVal) ? nameVal.value : isRuntimeString(nameVal) ? nameVal : String(nameVal);
35307
+ const fieldNames = [...s.fields.keys()];
35308
+ return RTV.classInstance(className, fieldNames, false, s.fields);
35309
+ }
35310
+ },
34985
35311
  {
34986
35312
  match: (argTypes) => {
34987
35313
  if (argTypes.length !== 1) return null;
@@ -35019,6 +35345,26 @@ defineBuiltin({
35019
35345
  }
35020
35346
  ]
35021
35347
  });
35348
+ for (const name of ["superiorto", "inferiorto"]) {
35349
+ defineBuiltin({
35350
+ name,
35351
+ help: {
35352
+ signatures: [`${name}('Class1', 'Class2', ...)`],
35353
+ description: name === "superiorto" ? "Establish superior class relationship (old-style class precedence). Accepted as a no-op." : "Establish inferior class relationship (old-style class precedence). Accepted as a no-op."
35354
+ },
35355
+ cases: [
35356
+ {
35357
+ match: (argTypes) => {
35358
+ for (const t of argTypes) {
35359
+ if (t.kind !== "char" && t.kind !== "string") return null;
35360
+ }
35361
+ return [];
35362
+ },
35363
+ apply: () => void 0
35364
+ }
35365
+ ]
35366
+ });
35367
+ }
35022
35368
  function fieldnamesApply(args) {
35023
35369
  if (args.length !== 1)
35024
35370
  throw new RuntimeError("fieldnames requires 1 argument");
@@ -35134,6 +35480,11 @@ function validateDim(x) {
35134
35480
  throw new RuntimeError("Size inputs must be nonnegative integers.");
35135
35481
  return Math.max(0, x);
35136
35482
  }
35483
+ function toScalarDim(v) {
35484
+ if (isRuntimeTensor(v) && v.data.length !== 1)
35485
+ throw new RuntimeError("Size inputs must be scalar.");
35486
+ return validateDim(toNumber(v));
35487
+ }
35137
35488
  function parseShapeArgs(args) {
35138
35489
  if (args.length === 1 && isRuntimeTensor(args[0])) {
35139
35490
  const t = args[0];
@@ -35141,7 +35492,7 @@ function parseShapeArgs(args) {
35141
35492
  for (let i = 0; i < t.data.length; i++) shape.push(validateDim(t.data[i]));
35142
35493
  return shape;
35143
35494
  }
35144
- return args.map((a) => validateDim(toNumber(a)));
35495
+ return args.map(toScalarDim);
35145
35496
  }
35146
35497
  function arrayConstructorCases(fillFn, scalarValue, opts) {
35147
35498
  const cases = [
@@ -35182,12 +35533,14 @@ function arrayConstructorCases(fillFn, scalarValue, opts) {
35182
35533
  return fillFn(shape);
35183
35534
  }
35184
35535
  },
35185
- // Multiple scalar args
35536
+ // Multiple scalar args. A 1×1 array counts as a scalar dimension, so
35537
+ // accept `tensor` here too (apply-time validates each is scalar).
35186
35538
  {
35187
35539
  match: (argTypes) => {
35188
35540
  if (argTypes.length <= 1) return null;
35189
35541
  for (const a of argTypes) {
35190
- if (a.kind !== "number" && a.kind !== "boolean") return null;
35542
+ if (a.kind !== "number" && a.kind !== "boolean" && a.kind !== "tensor")
35543
+ return null;
35191
35544
  }
35192
35545
  const shape = argTypes.map(
35193
35546
  (a) => a.kind === "number" && typeof a.exact === "number" ? a.exact : -1
@@ -35852,7 +36205,6 @@ function anyAllApply(name, mode) {
35852
36205
  if (isRuntimeComplexNumber(v)) return RTV.logical(v.re !== 0 || v.im !== 0);
35853
36206
  if (isRuntimeTensor(v)) {
35854
36207
  if (args.length === 1) {
35855
- if (v.data.length === 0) return RTV.logical(mode === "all");
35856
36208
  const d = firstReduceDim(v.shape);
35857
36209
  if (d === 0) return RTV.logical(scanLogical(v.data, v.imag, mode));
35858
36210
  return logicalAlongDim(v, d, mode);
@@ -36122,7 +36474,12 @@ function applyTextFn(v, fn) {
36122
36474
  }
36123
36475
  return RTV.cell(out, [...v.shape]);
36124
36476
  }
36125
- if (isRuntimeChar(v)) return RTV.char(fn(v.value));
36477
+ if (isRuntimeChar(v)) {
36478
+ if (v.shape && (v.shape[0] ?? 1) > 1) {
36479
+ return charRowsToMatrix(valueToCharRows(v).map(fn));
36480
+ }
36481
+ return RTV.char(fn(v.value));
36482
+ }
36126
36483
  if (isRuntimeString(v)) return RTV.string(fn(toString(v)));
36127
36484
  return v;
36128
36485
  }
@@ -36274,7 +36631,7 @@ registerIBuiltin({
36274
36631
  const str = toString(args[0]);
36275
36632
  const pat = toString(args[1]);
36276
36633
  const rep = toString(args[2]);
36277
- let flags = "g";
36634
+ let flags = "gs";
36278
36635
  for (let i = 3; i < args.length; i++) {
36279
36636
  const opt = toString(args[i]).toLowerCase();
36280
36637
  if (opt === "ignorecase") flags += "i";
@@ -36638,6 +36995,38 @@ registerIBuiltin({
36638
36995
  };
36639
36996
  }
36640
36997
  });
36998
+ registerIBuiltin({
36999
+ name: "isspace",
37000
+ help: {
37001
+ signatures: ["TF = isspace(str)"],
37002
+ description: "Return a logical array the same size as str, true where the character is whitespace. Numeric input is treated as Unicode code points."
37003
+ },
37004
+ resolve: (argTypes) => {
37005
+ if (argTypes.length !== 1) return null;
37006
+ const k = argTypes[0].kind;
37007
+ if (k !== "char" && k !== "string" && k !== "number" && k !== "boolean" && k !== "tensor") {
37008
+ return null;
37009
+ }
37010
+ const pred = (cp) => RE_WSPACE.test(String.fromCodePoint(cp));
37011
+ return {
37012
+ outputTypes: [{ kind: "tensor", isComplex: false, isLogical: true }],
37013
+ apply: (args) => {
37014
+ const v = args[0];
37015
+ if (isRuntimeChar(v)) {
37016
+ return logicalRowFromString(
37017
+ v.value,
37018
+ pred,
37019
+ v.shape ? [...v.shape] : void 0
37020
+ );
37021
+ }
37022
+ if (isRuntimeString(v)) return logicalRowFromString(toString(v), pred);
37023
+ if (isRuntimeTensor(v))
37024
+ return logicalFromNumericTensor(v.data, v.shape, pred);
37025
+ return logicalFromNumericTensor([toNumber(v)], [1, 1], pred);
37026
+ }
37027
+ };
37028
+ }
37029
+ });
36641
37030
  registerIBuiltin({
36642
37031
  name: "contains",
36643
37032
  resolve: (argTypes) => {
@@ -37085,12 +37474,102 @@ registerIBuiltin({
37085
37474
  };
37086
37475
  }
37087
37476
  });
37477
+ function collectRows(v) {
37478
+ if (isRuntimeCell(v)) {
37479
+ const rows = [];
37480
+ for (const el of v.data) rows.push(...valueToCharRows(el));
37481
+ return rows;
37482
+ }
37483
+ return valueToCharRows(v);
37484
+ }
37485
+ registerIBuiltin({
37486
+ name: "strmatch",
37487
+ help: {
37488
+ signatures: [
37489
+ "x = strmatch(str, strarray)",
37490
+ "x = strmatch(str, strarray, 'exact')"
37491
+ ],
37492
+ description: "(Legacy) Find rows of STRARRAY that begin with STR, or that equal STR when 'exact' is given. Returns a column vector of matching row indices."
37493
+ },
37494
+ resolve: (argTypes) => {
37495
+ if (argTypes.length < 2 || argTypes.length > 3) return null;
37496
+ if (!isTextType(argTypes[0])) return null;
37497
+ const sa = argTypes[1];
37498
+ if (sa.kind !== "char" && sa.kind !== "string" && sa.kind !== "cell")
37499
+ return null;
37500
+ return {
37501
+ outputTypes: [{ kind: "tensor", isComplex: false }],
37502
+ apply: (args) => {
37503
+ const str = toString(args[0]);
37504
+ let rows = collectRows(args[1]);
37505
+ const n = rows.length === 0 ? 0 : Math.max(...rows.map((r) => r.length));
37506
+ rows = rows.map((r) => r.padEnd(n, " "));
37507
+ const exactMatch = args.length === 3;
37508
+ let s = str;
37509
+ let len = s.length;
37510
+ if (len > n) {
37511
+ return RTV.tensor(allocFloat64Array(0), [0, 1]);
37512
+ }
37513
+ if (exactMatch && len < n) {
37514
+ const useNull = rows.some((r) => r.charCodeAt(n - 1) === 0);
37515
+ s = s.padEnd(n, useNull ? "\0" : " ");
37516
+ len = n;
37517
+ }
37518
+ const matches2 = [];
37519
+ for (let r = 0; r < rows.length; r++) {
37520
+ let ok = true;
37521
+ for (let i = 0; i < len; i++) {
37522
+ if (rows[r][i] !== s[i]) {
37523
+ ok = false;
37524
+ break;
37525
+ }
37526
+ }
37527
+ if (ok) matches2.push(r + 1);
37528
+ }
37529
+ return RTV.tensor(allocFloat64Array(matches2), [matches2.length, 1]);
37530
+ }
37531
+ };
37532
+ }
37533
+ });
37534
+ function valueToCharRows(v) {
37535
+ if (isRuntimeChar(v)) {
37536
+ const cols = v.shape ? v.shape[1] ?? v.value.length : v.value.length;
37537
+ const numRows = v.shape ? v.shape[0] ?? 1 : 1;
37538
+ if (numRows <= 1) return [v.value];
37539
+ const out = [];
37540
+ for (let r = 0; r < numRows; r++)
37541
+ out.push(v.value.slice(r * cols, (r + 1) * cols));
37542
+ return out;
37543
+ }
37544
+ if (isRuntimeString(v)) return [v];
37545
+ if (isRuntimeNumber(v)) return [String.fromCharCode(Math.round(v))];
37546
+ if (isRuntimeTensor(v)) {
37547
+ const rows = v.shape.length >= 2 ? v.shape[0] ?? 1 : 1;
37548
+ const cols = v.shape.length >= 2 ? v.shape[1] ?? 0 : v.data.length;
37549
+ const out = [];
37550
+ for (let r = 0; r < rows; r++) {
37551
+ let s = "";
37552
+ for (let c = 0; c < cols; c++)
37553
+ s += String.fromCharCode(Math.round(v.data[c * rows + r]));
37554
+ out.push(s);
37555
+ }
37556
+ return out;
37557
+ }
37558
+ throw new RuntimeError("char: unsupported cell element type");
37559
+ }
37560
+ function charRowsToMatrix(rows) {
37561
+ if (rows.length === 0) return RTV.char("");
37562
+ const width = Math.max(...rows.map((r) => r.length));
37563
+ const padded = rows.map((r) => r.padEnd(width, " "));
37564
+ if (rows.length === 1) return RTV.char(padded[0]);
37565
+ return new RuntimeChar(padded.join(""), [rows.length, width]);
37566
+ }
37088
37567
  registerIBuiltin({
37089
37568
  name: "char",
37090
37569
  resolve: (argTypes) => {
37091
37570
  if (argTypes.length !== 1) return null;
37092
37571
  const a = argTypes[0];
37093
- if (a.kind !== "char" && a.kind !== "string" && a.kind !== "number" && a.kind !== "tensor")
37572
+ if (a.kind !== "char" && a.kind !== "string" && a.kind !== "number" && a.kind !== "tensor" && a.kind !== "cell")
37094
37573
  return null;
37095
37574
  return {
37096
37575
  outputTypes: [{ kind: "char" }],
@@ -37107,6 +37586,11 @@ registerIBuiltin({
37107
37586
  }
37108
37587
  return RTV.char(chars.join(""));
37109
37588
  }
37589
+ if (isRuntimeCell(v)) {
37590
+ const rows = [];
37591
+ for (const el of v.data) rows.push(...valueToCharRows(el));
37592
+ return charRowsToMatrix(rows);
37593
+ }
37110
37594
  throw new RuntimeError("char: unsupported arguments");
37111
37595
  }
37112
37596
  };
@@ -37174,6 +37658,8 @@ registerIBuiltin({
37174
37658
  if (!isTextType(argTypes[0]) || !isTextType(argTypes[1])) return null;
37175
37659
  const outputTypes = [{ kind: "number" }];
37176
37660
  if (nargout >= 2) outputTypes.push({ kind: "number" });
37661
+ if (nargout >= 3) outputTypes.push({ kind: "char" });
37662
+ if (nargout >= 4) outputTypes.push({ kind: "number" });
37177
37663
  return {
37178
37664
  outputTypes,
37179
37665
  apply: (args, nout) => {
@@ -37183,6 +37669,7 @@ registerIBuiltin({
37183
37669
  const results = [];
37184
37670
  let strPos = 0;
37185
37671
  let fmtPos = 0;
37672
+ let matchFailure = false;
37186
37673
  while (fmtPos < fmt.length && strPos < str.length && results.length < maxCount) {
37187
37674
  if (fmt[fmtPos] === "%") {
37188
37675
  fmtPos++;
@@ -37194,22 +37681,34 @@ registerIBuiltin({
37194
37681
  }
37195
37682
  if (spec === "d" || spec === "i") {
37196
37683
  const m = str.slice(strPos).match(/^[+-]?\d+/);
37197
- if (!m) break;
37684
+ if (!m) {
37685
+ matchFailure = true;
37686
+ break;
37687
+ }
37198
37688
  results.push(parseInt(m[0], 10));
37199
37689
  strPos += m[0].length;
37200
37690
  } else if (spec === "f" || spec === "e" || spec === "g") {
37201
37691
  const m = str.slice(strPos).match(/^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?/);
37202
- if (!m) break;
37692
+ if (!m) {
37693
+ matchFailure = true;
37694
+ break;
37695
+ }
37203
37696
  results.push(parseFloat(m[0]));
37204
37697
  strPos += m[0].length;
37205
37698
  } else if (spec === "x") {
37206
37699
  const m = str.slice(strPos).match(/^[+-]?[0-9a-fA-F]+/);
37207
- if (!m) break;
37700
+ if (!m) {
37701
+ matchFailure = true;
37702
+ break;
37703
+ }
37208
37704
  results.push(parseInt(m[0], 16));
37209
37705
  strPos += m[0].length;
37210
37706
  } else if (spec === "o") {
37211
37707
  const m = str.slice(strPos).match(/^[+-]?[0-7]+/);
37212
- if (!m) break;
37708
+ if (!m) {
37709
+ matchFailure = true;
37710
+ break;
37711
+ }
37213
37712
  results.push(parseInt(m[0], 8));
37214
37713
  strPos += m[0].length;
37215
37714
  } else if (spec === "c") {
@@ -37217,7 +37716,10 @@ registerIBuiltin({
37217
37716
  strPos++;
37218
37717
  } else if (spec === "s") {
37219
37718
  const m = str.slice(strPos).match(/^\S+/);
37220
- if (!m) break;
37719
+ if (!m) {
37720
+ matchFailure = true;
37721
+ break;
37722
+ }
37221
37723
  for (let ci = 0; ci < m[0].length && results.length < maxCount; ci++) {
37222
37724
  results.push(m[0].charCodeAt(ci));
37223
37725
  }
@@ -37227,7 +37729,10 @@ registerIBuiltin({
37227
37729
  fmtPos++;
37228
37730
  while (strPos < str.length && /\s/.test(str[strPos])) strPos++;
37229
37731
  } else {
37230
- if (str[strPos] !== fmt[fmtPos]) break;
37732
+ if (str[strPos] !== fmt[fmtPos]) {
37733
+ matchFailure = true;
37734
+ break;
37735
+ }
37231
37736
  strPos++;
37232
37737
  fmtPos++;
37233
37738
  }
@@ -37237,7 +37742,16 @@ registerIBuiltin({
37237
37742
  }
37238
37743
  const vals = results.length === 1 ? RTV.num(results[0]) : RTV.tensor(allocFloat64Array(results), [results.length, 1]);
37239
37744
  if (nout >= 2) {
37240
- return [vals, RTV.num(results.length)];
37745
+ const out = [vals, RTV.num(results.length)];
37746
+ if (nout >= 3) {
37747
+ out.push(
37748
+ RTV.char(matchFailure ? "Matching failure in format." : "")
37749
+ );
37750
+ }
37751
+ if (nout >= 4) {
37752
+ out.push(RTV.num(strPos + 1));
37753
+ }
37754
+ return out;
37241
37755
  }
37242
37756
  return vals;
37243
37757
  }
@@ -38648,8 +39162,34 @@ function normImplTensor(v, args) {
38648
39162
  return RTV.num(maxRowSum);
38649
39163
  }
38650
39164
  if (p2 === 2) {
39165
+ let anyNaN = false;
39166
+ let anyInf = false;
39167
+ for (let i = 0; i < v.data.length; i++) {
39168
+ const re = v.data[i];
39169
+ const im = imag2 ? imag2[i] : 0;
39170
+ if (Number.isNaN(re) || Number.isNaN(im)) anyNaN = true;
39171
+ else if (!isFinite(re) || !isFinite(im)) anyInf = true;
39172
+ }
39173
+ if (anyNaN) return RTV.num(NaN);
39174
+ if (anyInf) return RTV.num(Infinity);
38651
39175
  const bridge = getEffectiveBridge("norm", "svd");
38652
39176
  if (bridge && bridge.svd) {
39177
+ if (imag2) {
39178
+ const R = allocFloat64Array(4 * rows * cols);
39179
+ const tm = 2 * rows;
39180
+ for (let j = 0; j < cols; j++) {
39181
+ for (let i = 0; i < rows; i++) {
39182
+ const a = v.data[j * rows + i];
39183
+ const b = imag2[j * rows + i];
39184
+ R[j * tm + i] = a;
39185
+ R[j * tm + (rows + i)] = b;
39186
+ R[(cols + j) * tm + i] = -b;
39187
+ R[(cols + j) * tm + (rows + i)] = a;
39188
+ }
39189
+ }
39190
+ const result2 = bridge.svd(R, tm, 2 * cols, false, false);
39191
+ return RTV.num(result2.S[0]);
39192
+ }
38653
39193
  const f64 = v.data instanceof Float64Array ? v.data : allocFloat64Array(v.data);
38654
39194
  const result = bridge.svd(f64, rows, cols, false, false);
38655
39195
  return RTV.num(result.S[0]);
@@ -39409,6 +39949,100 @@ function svdApply(args, nargout) {
39409
39949
  [k, 1]
39410
39950
  );
39411
39951
  }
39952
+ function nullApply(args) {
39953
+ let A = args[0];
39954
+ if (isRuntimeNumber(A) || isRuntimeComplexNumber(A)) {
39955
+ A = isRuntimeNumber(A) ? RTV.tensor(allocFloat64Array([A]), [1, 1]) : RTV.tensor(
39956
+ allocFloat64Array([A.re]),
39957
+ [1, 1],
39958
+ allocFloat64Array([A.im])
39959
+ );
39960
+ }
39961
+ if (!isRuntimeTensor(A))
39962
+ throw new RuntimeError("null: argument must be a numeric matrix");
39963
+ const [m, n] = tensorSize2D(A);
39964
+ const [, Sdiag, V] = svdApply([A], 3);
39965
+ const k = Math.min(m, n);
39966
+ const s = [];
39967
+ for (let i = 0; i < k; i++) s.push(Sdiag.data[colMajorIndex(i, i, m)]);
39968
+ const maxS = s.length > 0 ? Math.max(...s) : 0;
39969
+ const tol = args.length > 1 && args[1] !== void 0 ? toNumber(args[1]) : Math.max(m, n) * epsOf(maxS);
39970
+ let r = 0;
39971
+ for (const sv of s) if (sv > tol) r++;
39972
+ const cols = n - r;
39973
+ const out = allocFloat64Array(n * cols);
39974
+ const outImag = V.imag ? allocFloat64Array(n * cols) : void 0;
39975
+ for (let c = 0; c < cols; c++) {
39976
+ for (let i = 0; i < n; i++) {
39977
+ const src = colMajorIndex(i, r + c, n);
39978
+ out[colMajorIndex(i, c, n)] = V.data[src];
39979
+ if (outImag) outImag[colMajorIndex(i, c, n)] = V.imag[src];
39980
+ }
39981
+ }
39982
+ return RTV.tensor(out, [n, cols], outImag);
39983
+ }
39984
+ registerIBuiltin({
39985
+ name: "null",
39986
+ resolve: (argTypes, nargout) => {
39987
+ if (nargout > 1 || argTypes.length < 1 || argTypes.length > 2) return null;
39988
+ if (!isNumericJitType(argTypes[0])) return null;
39989
+ const isComplex = argTypes[0].kind === "tensor" && argTypes[0].isComplex;
39990
+ return {
39991
+ outputTypes: [tensorType(isComplex || void 0)],
39992
+ apply: (args) => nullApply(args)
39993
+ };
39994
+ }
39995
+ });
39996
+ function computeBandwidth(A) {
39997
+ let lower = 0;
39998
+ let upper = 0;
39999
+ const note = (i, j) => {
40000
+ if (i > j) lower = Math.max(lower, i - j);
40001
+ else if (j > i) upper = Math.max(upper, j - i);
40002
+ };
40003
+ if (isRuntimeNumber(A) || isRuntimeComplexNumber(A)) return [0, 0];
40004
+ if (isRuntimeSparseMatrix(A)) {
40005
+ for (let j = 0; j < A.n; j++) {
40006
+ for (let k = A.jc[j]; k < A.jc[j + 1]; k++) {
40007
+ if (A.pr[k] === 0 && (!A.pi || A.pi[k] === 0)) continue;
40008
+ note(A.ir[k], j);
40009
+ }
40010
+ }
40011
+ return [lower, upper];
40012
+ }
40013
+ if (isRuntimeTensor(A)) {
40014
+ const [m, n] = tensorSize2D(A);
40015
+ for (let j = 0; j < n; j++) {
40016
+ for (let i = 0; i < m; i++) {
40017
+ const idx = colMajorIndex(i, j, m);
40018
+ if (A.data[idx] === 0 && (!A.imag || A.imag[idx] === 0)) continue;
40019
+ note(i, j);
40020
+ }
40021
+ }
40022
+ return [lower, upper];
40023
+ }
40024
+ throw new RuntimeError("bandwidth: first argument must be a numeric matrix");
40025
+ }
40026
+ function bandwidthApply(args, nargout) {
40027
+ const [lower, upper] = computeBandwidth(args[0]);
40028
+ if (args.length >= 2 && args[1] !== void 0) {
40029
+ const type = parseStringArgLower(args[1]);
40030
+ if (type === "lower") return RTV.num(lower);
40031
+ if (type === "upper") return RTV.num(upper);
40032
+ throw new RuntimeError("bandwidth: TYPE must be 'lower' or 'upper'");
40033
+ }
40034
+ if (nargout >= 2) return [RTV.num(lower), RTV.num(upper)];
40035
+ return RTV.num(lower);
40036
+ }
40037
+ registerIBuiltin({
40038
+ name: "bandwidth",
40039
+ resolve: (argTypes, nargout) => {
40040
+ if (nargout > 2 || argTypes.length < 1 || argTypes.length > 2) return null;
40041
+ if (!isNumericJitType(argTypes[0])) return null;
40042
+ const outs = nargout >= 2 ? [NUM, NUM] : [NUM];
40043
+ return { outputTypes: outs, apply: (args, n) => bandwidthApply(args, n) };
40044
+ }
40045
+ });
39412
40046
  function computeATA(A_data, m, n) {
39413
40047
  const result = allocFloat64Array(n * n);
39414
40048
  for (let j = 0; j < n; j++)
@@ -41279,6 +41913,33 @@ function validateSizeArg(x) {
41279
41913
  }
41280
41914
  return x < 0 ? 0 : x;
41281
41915
  }
41916
+ function parseReshapeDims(args, total) {
41917
+ let rawDims;
41918
+ if (args.length === 2 && isRuntimeTensor(args[1]) && args[1].data.length > 1) {
41919
+ rawDims = Array.from(args[1].data).map((x) => validateSizeArg(x));
41920
+ } else {
41921
+ rawDims = args.slice(1).map((a) => {
41922
+ if (isRuntimeTensor(a) && a.data.length === 0) return null;
41923
+ return validateSizeArg(toNumber(a));
41924
+ });
41925
+ }
41926
+ const autoCount = rawDims.filter((d) => d === null).length;
41927
+ if (autoCount > 1)
41928
+ throw new RuntimeError("reshape: only one dimension size can be []");
41929
+ let shape;
41930
+ if (autoCount === 1) {
41931
+ const known = rawDims.filter((d) => d !== null);
41932
+ const knownProduct = known.reduce((a, b) => a * b, 1);
41933
+ if (knownProduct === 0 || total % knownProduct !== 0)
41934
+ throw new RuntimeError("reshape: number of elements must not change");
41935
+ shape = rawDims.map((d) => d === null ? total / knownProduct : d);
41936
+ } else {
41937
+ shape = rawDims;
41938
+ }
41939
+ if (numel(shape) !== total)
41940
+ throw new RuntimeError("reshape: number of elements must not change");
41941
+ return shape;
41942
+ }
41282
41943
  defineBuiltin({
41283
41944
  name: "reshape",
41284
41945
  cases: [
@@ -41290,31 +41951,31 @@ defineBuiltin({
41290
41951
  const v = args[0];
41291
41952
  if (isRuntimeSparseMatrix(v)) {
41292
41953
  const totalEl = v.m * v.n;
41293
- let rawDims2;
41954
+ let rawDims;
41294
41955
  if (args.length === 2 && isRuntimeTensor(args[1]) && args[1].data.length > 1) {
41295
- rawDims2 = Array.from(args[1].data).map((x) => Math.round(x));
41956
+ rawDims = Array.from(args[1].data).map((x) => Math.round(x));
41296
41957
  } else {
41297
- rawDims2 = args.slice(1).map((a) => {
41958
+ rawDims = args.slice(1).map((a) => {
41298
41959
  if (isRuntimeTensor(a) && a.data.length === 0) return null;
41299
41960
  return Math.round(toNumber(a));
41300
41961
  });
41301
41962
  }
41302
- const autoCount2 = rawDims2.filter((d) => d === null).length;
41303
- if (autoCount2 > 1)
41963
+ const autoCount = rawDims.filter((d) => d === null).length;
41964
+ if (autoCount > 1)
41304
41965
  throw new RuntimeError(
41305
41966
  "reshape: only one dimension size can be []"
41306
41967
  );
41307
41968
  let shape2;
41308
- if (autoCount2 === 1) {
41309
- const known = rawDims2.filter((d) => d !== null);
41969
+ if (autoCount === 1) {
41970
+ const known = rawDims.filter((d) => d !== null);
41310
41971
  const knownProduct = known.reduce((a, b) => a * b, 1);
41311
41972
  if (totalEl % knownProduct !== 0)
41312
41973
  throw new RuntimeError(
41313
41974
  "reshape: number of elements must not change"
41314
41975
  );
41315
- shape2 = rawDims2.map((d) => d === null ? totalEl / knownProduct : d);
41976
+ shape2 = rawDims.map((d) => d === null ? totalEl / knownProduct : d);
41316
41977
  } else {
41317
- shape2 = rawDims2;
41978
+ shape2 = rawDims;
41318
41979
  }
41319
41980
  if (shape2.length !== 2)
41320
41981
  throw new RuntimeError("reshape: sparse matrices must be 2-D");
@@ -41352,40 +42013,32 @@ defineBuiltin({
41352
42013
  jc[newN] = ti;
41353
42014
  return RTV.sparseMatrix(newM, newN, ir, jc, pr, pi2);
41354
42015
  }
42016
+ if (isRuntimeChar(v)) {
42017
+ const srcRows = v.shape ? v.shape[0] : 1;
42018
+ const srcCols = v.shape ? v.shape[1] ?? 0 : v.value.length;
42019
+ const total = v.value.length;
42020
+ const reqShape = parseReshapeDims(args, total);
42021
+ const s = [...reqShape];
42022
+ while (s.length > 2 && s[s.length - 1] === 1) s.pop();
42023
+ if (s.length > 2)
42024
+ throw new RuntimeError("reshape: char arrays must be 2-D");
42025
+ const nr = s[0];
42026
+ const nc = s.length >= 2 ? s[1] : 1;
42027
+ const linear = new Array(total);
42028
+ for (let j = 0; j < srcCols; j++)
42029
+ for (let i = 0; i < srcRows; i++)
42030
+ linear[j * srcRows + i] = v.value[i * srcCols + j];
42031
+ const chars = new Array(total);
42032
+ for (let j2 = 0; j2 < nc; j2++)
42033
+ for (let i2 = 0; i2 < nr; i2++)
42034
+ chars[i2 * nc + j2] = linear[j2 * nr + i2];
42035
+ return new RuntimeChar(chars.join(""), [nr, nc]);
42036
+ }
41355
42037
  if (!isRuntimeTensor(v) && !isRuntimeNumber(v) && !isRuntimeComplexNumber(v))
41356
42038
  throw new RuntimeError("reshape: first argument must be numeric");
41357
42039
  const data = isRuntimeTensor(v) ? v.data : isRuntimeComplexNumber(v) ? allocFloat64Array([v.re]) : allocFloat64Array([v]);
41358
42040
  const imag2 = isRuntimeTensor(v) ? v.imag : isRuntimeComplexNumber(v) ? allocFloat64Array([v.im]) : void 0;
41359
- let rawDims;
41360
- if (args.length === 2 && isRuntimeTensor(args[1]) && args[1].data.length > 1) {
41361
- rawDims = Array.from(args[1].data).map((x) => validateSizeArg(x));
41362
- } else {
41363
- rawDims = args.slice(1).map((a) => {
41364
- if (isRuntimeTensor(a) && a.data.length === 0) return null;
41365
- return validateSizeArg(toNumber(a));
41366
- });
41367
- }
41368
- const autoCount = rawDims.filter((d) => d === null).length;
41369
- if (autoCount > 1)
41370
- throw new RuntimeError("reshape: only one dimension size can be []");
41371
- let shape;
41372
- if (autoCount === 1) {
41373
- const known = rawDims.filter((d) => d !== null);
41374
- const knownProduct = known.reduce((a, b) => a * b, 1);
41375
- if (data.length % knownProduct !== 0)
41376
- throw new RuntimeError(
41377
- "reshape: number of elements must not change"
41378
- );
41379
- shape = rawDims.map(
41380
- (d) => d === null ? data.length / knownProduct : d
41381
- );
41382
- } else {
41383
- shape = rawDims;
41384
- }
41385
- const n = numel(shape);
41386
- if (n !== data.length) {
41387
- throw new RuntimeError("reshape: number of elements must not change");
41388
- }
42041
+ const shape = parseReshapeDims(args, data.length);
41389
42042
  if (isRuntimeTensor(v)) {
41390
42043
  const dataCopy = allocFloat64Array(data);
41391
42044
  const imagCopy = imag2 ? allocFloat64Array(imag2) : void 0;
@@ -41797,6 +42450,35 @@ defineBuiltin({
41797
42450
  }
41798
42451
  ]
41799
42452
  });
42453
+ function repmatObjects(v, repArgs) {
42454
+ const srcElements = isRuntimeClassInstanceArray(v) ? v.elements : [v];
42455
+ const [rows, cols] = isRuntimeClassInstanceArray(v) ? v.shape : [1, 1];
42456
+ const className = v.className;
42457
+ let reps;
42458
+ if (repArgs.length === 1) {
42459
+ const arg1 = repArgs[0];
42460
+ if (isRuntimeTensor(arg1)) {
42461
+ reps = Array.from(arg1.data).map((x) => validateSizeArg(x));
42462
+ } else {
42463
+ const n = validateSizeArg(toNumber(arg1));
42464
+ reps = [n, n];
42465
+ }
42466
+ } else {
42467
+ reps = repArgs.map((a) => validateSizeArg(toNumber(a)));
42468
+ }
42469
+ const rRep = reps[0] ?? 1;
42470
+ const cRep = reps.length >= 2 ? reps[1] : reps[0] ?? 1;
42471
+ const newRows = rows * rRep;
42472
+ const newCols = cols * cRep;
42473
+ const out = new Array(Math.max(0, newRows * newCols));
42474
+ for (let J = 0; J < newCols; J++) {
42475
+ for (let I = 0; I < newRows; I++) {
42476
+ const src = srcElements[I % rows + J % cols * rows];
42477
+ out[I + J * newRows] = copyClassInstance(src);
42478
+ }
42479
+ }
42480
+ return out.length === 1 ? out[0] : RTV.classInstanceArray(className, out, [newRows, newCols]);
42481
+ }
41800
42482
  defineBuiltin({
41801
42483
  name: "repmat",
41802
42484
  cases: [
@@ -41821,6 +42503,8 @@ defineBuiltin({
41821
42503
  if (args.length < 2)
41822
42504
  throw new RuntimeError("repmat requires at least 2 arguments");
41823
42505
  let v = args[0];
42506
+ if (isRuntimeClassInstance(v) || isRuntimeClassInstanceArray(v))
42507
+ return repmatObjects(v, args.slice(1));
41824
42508
  if (isRuntimeSparseMatrix(v)) v = sparseToDense(v);
41825
42509
  let reps;
41826
42510
  if (args.length === 2) {
@@ -41985,6 +42669,62 @@ defineBuiltin({
41985
42669
  }
41986
42670
  ]
41987
42671
  });
42672
+ function copyClassInstance(inst) {
42673
+ if (inst.isHandleClass) return inst;
42674
+ return new RuntimeClassInstance(
42675
+ inst.className,
42676
+ new Map(inst.fields),
42677
+ inst.isHandleClass,
42678
+ inst._builtinData
42679
+ );
42680
+ }
42681
+ function repelemObjects(v, repArgs) {
42682
+ const srcElements = isRuntimeClassInstanceArray(v) ? v.elements : [v];
42683
+ const [rows, cols] = isRuntimeClassInstanceArray(v) ? v.shape : [1, 1];
42684
+ const className = v.className;
42685
+ const wrap = (elements, shape) => elements.length === 1 ? elements[0] : RTV.classInstanceArray(className, elements, shape);
42686
+ if (repArgs.length === 1) {
42687
+ const repArg = repArgs[0];
42688
+ if (isRuntimeTensor(repArg) && repArg.data.length > 1) {
42689
+ const counts = repArg.data;
42690
+ if (counts.length !== srcElements.length)
42691
+ throw new RuntimeError(
42692
+ `repelem: counts vector length (${counts.length}) must match the number of elements (${srcElements.length})`
42693
+ );
42694
+ const out3 = [];
42695
+ for (let i = 0; i < srcElements.length; i++) {
42696
+ const c = Math.max(0, Math.round(counts[i]));
42697
+ for (let j = 0; j < c; j++) out3.push(copyClassInstance(srcElements[i]));
42698
+ }
42699
+ const isCol2 = cols === 1 && rows !== 1;
42700
+ return wrap(out3, isCol2 ? [out3.length, 1] : [1, out3.length]);
42701
+ }
42702
+ const n = Math.max(0, Math.round(toNumber(repArg)));
42703
+ const out2 = [];
42704
+ for (const el of srcElements)
42705
+ for (let j = 0; j < n; j++) out2.push(copyClassInstance(el));
42706
+ const isCol = cols === 1 && rows !== 1;
42707
+ return wrap(out2, isCol ? [out2.length, 1] : [1, out2.length]);
42708
+ }
42709
+ const rRep = Math.max(0, Math.round(toNumber(repArgs[0])));
42710
+ const cRep = Math.max(0, Math.round(toNumber(repArgs[1])));
42711
+ const newRows = rows * rRep;
42712
+ const newCols = cols * cRep;
42713
+ const out = new Array(newRows * newCols);
42714
+ for (let c = 0; c < cols; c++) {
42715
+ for (let r = 0; r < rows; r++) {
42716
+ const src = srcElements[c * rows + r];
42717
+ for (let dc = 0; dc < cRep; dc++) {
42718
+ for (let dr = 0; dr < rRep; dr++) {
42719
+ const dstRow = r * rRep + dr;
42720
+ const dstCol = c * cRep + dc;
42721
+ out[dstCol * newRows + dstRow] = copyClassInstance(src);
42722
+ }
42723
+ }
42724
+ }
42725
+ }
42726
+ return wrap(out, [newRows, newCols]);
42727
+ }
41988
42728
  defineBuiltin({
41989
42729
  name: "repelem",
41990
42730
  cases: [
@@ -41994,6 +42734,8 @@ defineBuiltin({
41994
42734
  if (args.length < 2)
41995
42735
  throw new RuntimeError("repelem requires at least 2 arguments");
41996
42736
  const v = args[0];
42737
+ if (isRuntimeClassInstance(v) || isRuntimeClassInstanceArray(v))
42738
+ return repelemObjects(v, args.slice(1));
41997
42739
  if (args.length === 2) {
41998
42740
  const repArg = args[1];
41999
42741
  if (isRuntimeTensor(repArg) && repArg.data.length > 1) {
@@ -42116,7 +42858,15 @@ defineBuiltin({
42116
42858
  }
42117
42859
  return RTV.tensor(dataCopy, newShape, imagCopy);
42118
42860
  }
42119
- throw new RuntimeError("squeeze: argument must be numeric");
42861
+ if (isRuntimeCell(v)) {
42862
+ const shape = [...v.shape];
42863
+ while (shape.length > 2 && shape[shape.length - 1] === 1) shape.pop();
42864
+ if (shape.length <= 2) return RTV.cell(v.data, shape);
42865
+ const newShape = shape.filter((d) => d !== 1);
42866
+ while (newShape.length < 2) newShape.push(1);
42867
+ return RTV.cell(v.data, newShape);
42868
+ }
42869
+ return v;
42120
42870
  }
42121
42871
  }
42122
42872
  ]
@@ -42786,6 +43536,8 @@ defineBuiltin({
42786
43536
  ]
42787
43537
  });
42788
43538
  function bitwiseOp(a, b, op, name) {
43539
+ if (typeof a === "boolean") a = a ? 1 : 0;
43540
+ if (typeof b === "boolean") b = b ? 1 : 0;
42789
43541
  if (isRuntimeNumber(a) && isRuntimeNumber(b)) {
42790
43542
  return RTV.num(op(Math.round(a), Math.round(b)));
42791
43543
  }
@@ -45053,6 +45805,56 @@ defineBuiltin({
45053
45805
  }
45054
45806
  ]
45055
45807
  });
45808
+ function froundArray(src) {
45809
+ const out = allocFloat64Array(src.length);
45810
+ for (let i = 0; i < src.length; i++) out[i] = Math.fround(src[i]);
45811
+ return out;
45812
+ }
45813
+ function singleApply(v) {
45814
+ if (isRuntimeChar(v)) {
45815
+ if (v.value.length === 0) return RTV.tensor(allocFloat64Array(0), [0, 0]);
45816
+ if (v.value.length === 1)
45817
+ return RTV.num(Math.fround(v.value.charCodeAt(0)));
45818
+ return RTV.row(Array.from(v.value).map((c) => Math.fround(c.charCodeAt(0))));
45819
+ }
45820
+ if (isRuntimeLogical(v)) return RTV.num(v ? 1 : 0);
45821
+ if (isRuntimeNumber(v)) return RTV.num(Math.fround(v));
45822
+ if (isRuntimeComplexNumber(v))
45823
+ return RTV.complex(Math.fround(v.re), Math.fround(v.im));
45824
+ if (isRuntimeTensor(v)) {
45825
+ const imag2 = v.imag ? froundArray(v.imag) : void 0;
45826
+ return RTV.tensor(froundArray(v.data), v.shape.slice(), imag2);
45827
+ }
45828
+ if (isRuntimeClassInstance(v) && v._builtinData !== void 0)
45829
+ return singleApply(v._builtinData);
45830
+ return RTV.num(Math.fround(toNumber(v)));
45831
+ }
45832
+ defineBuiltin({
45833
+ name: "single",
45834
+ cases: [
45835
+ {
45836
+ match: (argTypes) => {
45837
+ if (argTypes.length !== 1) return null;
45838
+ const a = argTypes[0];
45839
+ if (a.kind === "number" || a.kind === "boolean" || a.kind === "char" || a.kind === "class_instance")
45840
+ return [{ kind: "number" }];
45841
+ if (a.kind === "complex_or_number")
45842
+ return [{ kind: "complex_or_number" }];
45843
+ if (a.kind === "tensor")
45844
+ return [
45845
+ {
45846
+ kind: "tensor",
45847
+ isComplex: a.isComplex,
45848
+ shape: a.shape,
45849
+ ndim: a.ndim
45850
+ }
45851
+ ];
45852
+ return null;
45853
+ },
45854
+ apply: (args) => singleApply(args[0])
45855
+ }
45856
+ ]
45857
+ });
45056
45858
  var INT_RANGES = [
45057
45859
  { name: "int8", min: -128, max: 127 },
45058
45860
  { name: "int16", min: -32768, max: 32767 },
@@ -45607,11 +46409,20 @@ defineBuiltin({
45607
46409
  },
45608
46410
  apply: (args) => {
45609
46411
  const v = args[0];
45610
- if (isRuntimeStructArray(v))
45611
- return RTV.logical(v.fieldNames.includes(toString(args[1])));
45612
- if (!isRuntimeStruct(v) && !isRuntimeClassInstance(v))
45613
- return RTV.logical(false);
45614
- return RTV.logical(v.fields.has(toString(args[1])));
46412
+ const hasField = (name) => {
46413
+ if (isRuntimeStructArray(v)) return v.fieldNames.includes(name);
46414
+ if (!isRuntimeStruct(v) && !isRuntimeClassInstance(v)) return false;
46415
+ return v.fields.has(name);
46416
+ };
46417
+ if (isRuntimeCell(args[1])) {
46418
+ const cell = args[1];
46419
+ const result = allocFloat64Array(cell.data.length);
46420
+ for (let i = 0; i < cell.data.length; i++) {
46421
+ result[i] = hasField(toString(cell.data[i])) ? 1 : 0;
46422
+ }
46423
+ return new RuntimeTensor(result, cell.shape.slice(), void 0, true);
46424
+ }
46425
+ return RTV.logical(hasField(toString(args[1])));
45615
46426
  }
45616
46427
  }
45617
46428
  ]
@@ -45728,7 +46539,7 @@ function regexpImpl(caseSensitive, name, args, nargout) {
45728
46539
  if (outModes.length === 0) {
45729
46540
  outModes.push("start", "end", "tokenextents", "match", "tokens", "names");
45730
46541
  }
45731
- const flags = caseSensitive ? "g" : "gi";
46542
+ const flags = caseSensitive ? "gs" : "gis";
45732
46543
  const re = new RegExp(pat, flags);
45733
46544
  const starts = [];
45734
46545
  const ends = [];
@@ -46448,6 +47259,13 @@ registerIBuiltin({
46448
47259
  if (args.length === 1) return RTV.cell([A], [1, 1]);
46449
47260
  A = RTV.tensor(allocFloat64Array([A ? 1 : 0]), [1, 1]);
46450
47261
  }
47262
+ if (isRuntimeStruct(A)) {
47263
+ return RTV.cell([A], [1, 1]);
47264
+ }
47265
+ if (isRuntimeStructArray(A)) {
47266
+ const elems = A.elements;
47267
+ return RTV.cell([...elems], [1, elems.length]);
47268
+ }
46451
47269
  if (!isRuntimeTensor(A))
46452
47270
  throw new RuntimeError(
46453
47271
  "num2cell: first argument must be a numeric array"
@@ -46571,6 +47389,69 @@ registerIBuiltin({
46571
47389
  };
46572
47390
  }
46573
47391
  });
47392
+ registerIBuiltin({
47393
+ name: "getfield",
47394
+ resolve: (argTypes) => {
47395
+ if (argTypes.length < 2) return null;
47396
+ return {
47397
+ outputTypes: [{ kind: "unknown" }],
47398
+ apply: (args) => {
47399
+ let v = args[0];
47400
+ for (let i = 1; i < args.length; i++) {
47401
+ if (isRuntimeCell(args[i]))
47402
+ throw new RuntimeError(
47403
+ "getfield: index ({}) subscripts are not supported"
47404
+ );
47405
+ if (!isRuntimeStruct(v))
47406
+ throw new RuntimeError("getfield: argument must be a structure");
47407
+ const name = toString(args[i]);
47408
+ if (!v.fields.has(name))
47409
+ throw new RuntimeError(
47410
+ `Reference to non-existent field '${name}'.`
47411
+ );
47412
+ v = v.fields.get(name);
47413
+ }
47414
+ return v;
47415
+ }
47416
+ };
47417
+ }
47418
+ });
47419
+ registerIBuiltin({
47420
+ name: "setfield",
47421
+ resolve: (argTypes) => {
47422
+ if (argTypes.length < 3) return null;
47423
+ return {
47424
+ outputTypes: [{ kind: "struct", fields: {} }],
47425
+ apply: (args) => {
47426
+ const value = args[args.length - 1];
47427
+ const fieldArgs = args.slice(1, args.length - 1);
47428
+ const setChain = (s, depth) => {
47429
+ const fa = fieldArgs[depth];
47430
+ if (isRuntimeCell(fa))
47431
+ throw new RuntimeError(
47432
+ "setfield: index ({}) subscripts are not supported"
47433
+ );
47434
+ const name = toString(fa);
47435
+ const base = isRuntimeStruct(s) ? s.fields : /* @__PURE__ */ new Map();
47436
+ if (!isRuntimeStruct(s) && !(isRuntimeTensor(s) && s.data.length === 0))
47437
+ throw new RuntimeError("setfield: argument must be a structure");
47438
+ const newFields = new Map(base);
47439
+ if (depth === fieldArgs.length - 1) {
47440
+ newFields.set(name, value);
47441
+ } else {
47442
+ const child = newFields.get(name);
47443
+ newFields.set(
47444
+ name,
47445
+ setChain(child ?? RTV.struct(/* @__PURE__ */ new Map()), depth + 1)
47446
+ );
47447
+ }
47448
+ return RTV.struct(newFields);
47449
+ };
47450
+ return setChain(args[0], 0);
47451
+ }
47452
+ };
47453
+ }
47454
+ });
46574
47455
  registerIBuiltin({
46575
47456
  name: "namedargs2cell",
46576
47457
  resolve: (argTypes) => {
@@ -46592,6 +47473,10 @@ registerIBuiltin({
46592
47473
  };
46593
47474
  }
46594
47475
  });
47476
+ function rmfieldNames(arg) {
47477
+ if (isRuntimeCell(arg)) return arg.data.map(toString);
47478
+ return [toString(arg)];
47479
+ }
46595
47480
  registerIBuiltin({
46596
47481
  name: "rmfield",
46597
47482
  resolve: (argTypes) => {
@@ -46600,25 +47485,26 @@ registerIBuiltin({
46600
47485
  outputTypes: [{ kind: "struct", fields: {} }],
46601
47486
  apply: (args) => {
46602
47487
  const v = args[0];
47488
+ const names = rmfieldNames(args[1]);
46603
47489
  if (isRuntimeStructArray(v)) {
46604
- const name2 = toString(args[1]);
46605
- if (!v.fieldNames.includes(name2))
46606
- throw new RuntimeError(`rmfield: field '${name2}' does not exist`);
46607
- const newFieldNames = v.fieldNames.filter((n) => n !== name2);
47490
+ for (const name of names)
47491
+ if (!v.fieldNames.includes(name))
47492
+ throw new RuntimeError(`rmfield: field '${name}' does not exist`);
47493
+ const newFieldNames = v.fieldNames.filter((n) => !names.includes(n));
46608
47494
  const newElements = v.elements.map((el) => {
46609
47495
  const newFields2 = new Map(el.fields);
46610
- newFields2.delete(name2);
47496
+ for (const name of names) newFields2.delete(name);
46611
47497
  return RTV.struct(newFields2);
46612
47498
  });
46613
47499
  return RTV.structArray(newFieldNames, newElements);
46614
47500
  }
46615
47501
  if (!isRuntimeStruct(v))
46616
47502
  throw new RuntimeError("rmfield: first argument must be a struct");
46617
- const name = toString(args[1]);
46618
- if (!v.fields.has(name))
46619
- throw new RuntimeError(`rmfield: field '${name}' does not exist`);
47503
+ for (const name of names)
47504
+ if (!v.fields.has(name))
47505
+ throw new RuntimeError(`rmfield: field '${name}' does not exist`);
46620
47506
  const newFields = new Map(v.fields);
46621
- newFields.delete(name);
47507
+ for (const name of names) newFields.delete(name);
46622
47508
  return RTV.struct(newFields);
46623
47509
  }
46624
47510
  };
@@ -47259,6 +48145,23 @@ registerIBuiltin({
47259
48145
  }
47260
48146
  })
47261
48147
  });
48148
+ registerIBuiltin({
48149
+ name: "spalloc",
48150
+ resolve: () => ({
48151
+ outputTypes: [{ kind: "unknown" }],
48152
+ apply: (args) => {
48153
+ const m = args.length >= 1 ? Math.round(toNumber(args[0])) : 0;
48154
+ const n = args.length >= 2 ? Math.round(toNumber(args[1])) : 0;
48155
+ return RTV.sparseMatrix(
48156
+ m,
48157
+ n,
48158
+ new Int32Array(0),
48159
+ new Int32Array(n + 1),
48160
+ allocFloat64Array(0)
48161
+ );
48162
+ }
48163
+ })
48164
+ });
47262
48165
  registerIBuiltin({
47263
48166
  name: "speye",
47264
48167
  resolve: () => ({
@@ -47544,6 +48447,598 @@ registerIBuiltin({
47544
48447
  }
47545
48448
  })
47546
48449
  });
48450
+ var SPPARMS_KEYS = [
48451
+ "spumoni",
48452
+ "thr_rel",
48453
+ "thr_abs",
48454
+ "exact_d",
48455
+ "supernd",
48456
+ "rreduce",
48457
+ "wh_frac",
48458
+ "autommd",
48459
+ "autoamd",
48460
+ "piv_tol",
48461
+ "bandden",
48462
+ "umfpack",
48463
+ "sym_tol",
48464
+ "ldl_tol",
48465
+ "usema57",
48466
+ "spqrtol",
48467
+ "sp_ctor",
48468
+ "reorder",
48469
+ "no_redo"
48470
+ ];
48471
+ var SPPARMS_DEFAULTS = [
48472
+ 0,
48473
+ 1.1,
48474
+ 1,
48475
+ 0,
48476
+ 3,
48477
+ 3,
48478
+ 0.5,
48479
+ 1,
48480
+ 1,
48481
+ 0.1,
48482
+ 0.5,
48483
+ 1,
48484
+ 1e-3,
48485
+ 0.01,
48486
+ 1,
48487
+ -2,
48488
+ 0,
48489
+ 0,
48490
+ 0
48491
+ ];
48492
+ var spparmsState = Float64Array.from(SPPARMS_DEFAULTS);
48493
+ function spparmsVector() {
48494
+ return RTV.tensor(allocFloat64Array(Array.from(spparmsState)), [
48495
+ spparmsState.length,
48496
+ 1
48497
+ ]);
48498
+ }
48499
+ registerIBuiltin({
48500
+ name: "spparms",
48501
+ resolve: (argTypes, nargout) => {
48502
+ if (argTypes.length > 2) return null;
48503
+ const tensorOut = { kind: "tensor", isComplex: false };
48504
+ const outputTypes = nargout >= 2 ? [{ kind: "char" }, tensorOut] : [tensorOut];
48505
+ return {
48506
+ outputTypes,
48507
+ apply: (args, n) => {
48508
+ if (args.length === 0) {
48509
+ if (n >= 2) {
48510
+ const width = Math.max(...SPPARMS_KEYS.map((k) => k.length));
48511
+ const padded = SPPARMS_KEYS.map((k) => k.padEnd(width)).join("");
48512
+ return [
48513
+ new RuntimeChar(padded, [SPPARMS_KEYS.length, width]),
48514
+ spparmsVector()
48515
+ ];
48516
+ }
48517
+ return spparmsVector();
48518
+ }
48519
+ const first = args[0];
48520
+ if (isRuntimeChar(first) || isRuntimeString(first)) {
48521
+ const key = parseStringArgLower(first);
48522
+ if (args.length >= 2) {
48523
+ const idx2 = SPPARMS_KEYS.indexOf(key);
48524
+ if (idx2 >= 0) spparmsState[idx2] = toNumber(args[1]);
48525
+ return RTV.num(0);
48526
+ }
48527
+ if (key === "default") {
48528
+ spparmsState = Float64Array.from(SPPARMS_DEFAULTS);
48529
+ return RTV.num(0);
48530
+ }
48531
+ if (key === "tight") {
48532
+ return RTV.num(0);
48533
+ }
48534
+ const idx = SPPARMS_KEYS.indexOf(key);
48535
+ if (idx >= 0) return RTV.num(spparmsState[idx]);
48536
+ throw new RuntimeError(`spparms: unknown parameter '${key}'`);
48537
+ }
48538
+ const vals = isRuntimeNumber(first) ? [first] : isRuntimeTensor(first) ? Array.from(first.data) : null;
48539
+ if (vals === null)
48540
+ throw new RuntimeError(
48541
+ "spparms: argument must be a parameter name or value vector"
48542
+ );
48543
+ const next = Float64Array.from(spparmsState);
48544
+ for (let i = 0; i < Math.min(vals.length, next.length); i++)
48545
+ next[i] = vals[i];
48546
+ spparmsState = next;
48547
+ return RTV.num(0);
48548
+ }
48549
+ };
48550
+ }
48551
+ });
48552
+
48553
+ // src/numbl-core/interpreter/builtins/graph.ts
48554
+ function isGraph(v) {
48555
+ return isRuntimeClassInstance(v) && v.className === "graph";
48556
+ }
48557
+ function requireGraph(v, fn) {
48558
+ if (!isGraph(v)) {
48559
+ throw new RuntimeError(`${fn}: first argument must be a graph`);
48560
+ }
48561
+ return v;
48562
+ }
48563
+ function graphN(g) {
48564
+ return toNumber(g.fields.get("_n") ?? 0);
48565
+ }
48566
+ function graphWeighted(g) {
48567
+ const w = g.fields.get("_weighted");
48568
+ return w === true;
48569
+ }
48570
+ function graphAdj(g) {
48571
+ const a = g.fields.get("_A");
48572
+ if (a === void 0 || !isRuntimeSparseMatrix(a)) {
48573
+ throw new RuntimeError("graph: corrupt internal adjacency");
48574
+ }
48575
+ return a;
48576
+ }
48577
+ function buildSymAdj(n, edges) {
48578
+ const triplets = [];
48579
+ for (const e of edges) {
48580
+ if (e.w === 0) continue;
48581
+ const u = e.u - 1;
48582
+ const v = e.v - 1;
48583
+ if (u === v) {
48584
+ triplets.push({ col: u, row: u, val: e.w });
48585
+ } else {
48586
+ triplets.push({ col: v, row: u, val: e.w });
48587
+ triplets.push({ col: u, row: v, val: e.w });
48588
+ }
48589
+ }
48590
+ triplets.sort((a, b) => a.col - b.col || a.row - b.row);
48591
+ const ir = [];
48592
+ const pr = [];
48593
+ const cols = [];
48594
+ let prevCol = -1;
48595
+ let prevRow = -1;
48596
+ for (const t of triplets) {
48597
+ if (t.col === prevCol && t.row === prevRow) {
48598
+ pr[pr.length - 1] += t.val;
48599
+ } else {
48600
+ ir.push(t.row);
48601
+ pr.push(t.val);
48602
+ cols.push(t.col);
48603
+ prevCol = t.col;
48604
+ prevRow = t.row;
48605
+ }
48606
+ }
48607
+ const jc = new Int32Array(n + 1);
48608
+ let ci = 0;
48609
+ for (let c = 0; c < n; c++) {
48610
+ jc[c] = ci;
48611
+ while (ci < cols.length && cols[ci] === c) ci++;
48612
+ }
48613
+ jc[n] = ci;
48614
+ return RTV.sparseMatrix(n, n, new Int32Array(ir), jc, allocFloat64Array(pr));
48615
+ }
48616
+ function makeGraph(n, edges, weighted) {
48617
+ const fields = /* @__PURE__ */ new Map();
48618
+ fields.set("_n", n);
48619
+ fields.set("_A", buildSymAdj(n, edges));
48620
+ fields.set("_weighted", weighted);
48621
+ return new RuntimeClassInstance("graph", fields, false);
48622
+ }
48623
+ function eachNeighbor(A, c, cb) {
48624
+ for (let k = A.jc[c]; k < A.jc[c + 1]; k++) {
48625
+ const r = A.ir[k];
48626
+ if (r !== c) cb(r, A.pr[k]);
48627
+ }
48628
+ }
48629
+ function edgesFromAdj(A) {
48630
+ const edges = [];
48631
+ for (let c = 0; c < A.n; c++) {
48632
+ for (let k = A.jc[c]; k < A.jc[c + 1]; k++) {
48633
+ const r = A.ir[k];
48634
+ if (r < c) edges.push({ u: r + 1, v: c + 1, w: A.pr[k] });
48635
+ else if (r === c) edges.push({ u: r + 1, v: r + 1, w: A.pr[k] });
48636
+ }
48637
+ }
48638
+ return edges;
48639
+ }
48640
+ function squareDim(A, fn) {
48641
+ if (isRuntimeNumber(A) || isRuntimeLogical(A)) return 1;
48642
+ if (isRuntimeSparseMatrix(A)) {
48643
+ if (A.m !== A.n) throw new RuntimeError(`${fn}: adjacency must be square`);
48644
+ return A.n;
48645
+ }
48646
+ if (isRuntimeTensor(A)) {
48647
+ const rows = A.shape[0] ?? 1;
48648
+ const cols = A.shape[1] ?? 1;
48649
+ if (rows !== cols)
48650
+ throw new RuntimeError(`${fn}: adjacency must be square`);
48651
+ return rows;
48652
+ }
48653
+ throw new RuntimeError(`${fn}: adjacency must be a numeric matrix`);
48654
+ }
48655
+ function takeEntry(r, c, triangle) {
48656
+ if (r === c) return true;
48657
+ if (triangle === "lower") return r > c;
48658
+ return r < c;
48659
+ }
48660
+ function adjToEdges(A, omitSelfLoops, triangle, fn) {
48661
+ const n = squareDim(A, fn);
48662
+ const weighted = !(isRuntimeTensor(A) && A._isLogical);
48663
+ const edges = [];
48664
+ const push = (r, c, val) => {
48665
+ if (val === 0) return;
48666
+ if (r === c) {
48667
+ if (!omitSelfLoops) edges.push({ u: r + 1, v: r + 1, w: val });
48668
+ } else if (takeEntry(r, c, triangle)) {
48669
+ edges.push({ u: Math.min(r, c) + 1, v: Math.max(r, c) + 1, w: val });
48670
+ }
48671
+ };
48672
+ if (isRuntimeSparseMatrix(A)) {
48673
+ for (let c = 0; c < A.n; c++) {
48674
+ for (let k = A.jc[c]; k < A.jc[c + 1]; k++) push(A.ir[k], c, A.pr[k]);
48675
+ }
48676
+ } else if (isRuntimeTensor(A)) {
48677
+ const m = A.shape[0] ?? 1;
48678
+ for (let c = 0; c < n; c++) {
48679
+ for (let r = 0; r < n; r++) {
48680
+ const val = A.data[c * m + r];
48681
+ if (val !== 0) push(r, c, val);
48682
+ }
48683
+ }
48684
+ } else if (isRuntimeNumber(A) || isRuntimeLogical(A)) {
48685
+ const val = isRuntimeNumber(A) ? A : A ? 1 : 0;
48686
+ if (val !== 0) push(0, 0, val);
48687
+ }
48688
+ return { n, edges, weighted };
48689
+ }
48690
+ function isNumericArg2(v) {
48691
+ return isRuntimeNumber(v) || isRuntimeLogical(v) || isRuntimeTensor(v) || isRuntimeSparseMatrix(v);
48692
+ }
48693
+ function toNumArray2(v) {
48694
+ if (isRuntimeNumber(v)) return [v];
48695
+ if (isRuntimeLogical(v)) return [v ? 1 : 0];
48696
+ if (isRuntimeTensor(v)) return Array.from(v.data);
48697
+ throw new RuntimeError("graph: node/weight arguments must be numeric");
48698
+ }
48699
+ function argString(v) {
48700
+ if (isRuntimeString(v)) return v;
48701
+ if (isRuntimeChar(v)) return v.value;
48702
+ return null;
48703
+ }
48704
+ registerIBuiltin({
48705
+ name: "graph",
48706
+ help: {
48707
+ signatures: [
48708
+ "G = graph(A)",
48709
+ "G = graph(A, 'omitselfloops')",
48710
+ "G = graph(A, 'upper' | 'lower')",
48711
+ "G = graph(s, t)",
48712
+ "G = graph(s, t, w)",
48713
+ "G = graph(s, t, w, num)"
48714
+ ],
48715
+ description: "Create an undirected graph from an adjacency matrix A (which must be symmetric) or from edge endpoint lists s and t with optional weights w."
48716
+ },
48717
+ resolve: () => ({
48718
+ outputTypes: [
48719
+ {
48720
+ kind: "class_instance",
48721
+ className: "graph",
48722
+ isHandleClass: false,
48723
+ fields: {}
48724
+ }
48725
+ ],
48726
+ apply: (args) => {
48727
+ if (args.length === 0) return makeGraph(0, [], false);
48728
+ if (args.length >= 2 && isNumericArg2(args[1])) {
48729
+ const s = toNumArray2(args[0]);
48730
+ const t = toNumArray2(args[1]);
48731
+ if (s.length !== t.length) {
48732
+ throw new RuntimeError("graph: s and t must have the same length");
48733
+ }
48734
+ let weighted2 = false;
48735
+ let w = null;
48736
+ let num = null;
48737
+ let omitSelfLoops2 = false;
48738
+ if (args.length >= 3 && isNumericArg2(args[2])) {
48739
+ w = toNumArray2(args[2]);
48740
+ weighted2 = true;
48741
+ if (args.length >= 4 && isNumericArg2(args[3])) {
48742
+ num = Math.floor(toNumber(args[3]));
48743
+ }
48744
+ }
48745
+ for (let i = 2; i < args.length; i++) {
48746
+ const str = argString(args[i]);
48747
+ if (str && str.toLowerCase() === "omitselfloops")
48748
+ omitSelfLoops2 = true;
48749
+ }
48750
+ let n2 = num ?? 0;
48751
+ for (let i = 0; i < s.length; i++) n2 = Math.max(n2, s[i], t[i]);
48752
+ const edges2 = [];
48753
+ for (let i = 0; i < s.length; i++) {
48754
+ const u = Math.min(s[i], t[i]);
48755
+ const v = Math.max(s[i], t[i]);
48756
+ if (omitSelfLoops2 && u === v) continue;
48757
+ edges2.push({ u, v, w: w ? w[w.length === 1 ? 0 : i] : 1 });
48758
+ }
48759
+ return makeGraph(n2, edges2, weighted2);
48760
+ }
48761
+ let omitSelfLoops = false;
48762
+ let triangle = "sym";
48763
+ for (let i = 1; i < args.length; i++) {
48764
+ const str = argString(args[i]);
48765
+ if (!str) continue;
48766
+ const lc = str.toLowerCase();
48767
+ if (lc === "omitselfloops") omitSelfLoops = true;
48768
+ else if (lc === "upper") triangle = "upper";
48769
+ else if (lc === "lower") triangle = "lower";
48770
+ }
48771
+ const { n, edges, weighted } = adjToEdges(
48772
+ args[0],
48773
+ omitSelfLoops,
48774
+ triangle,
48775
+ "graph"
48776
+ );
48777
+ return makeGraph(n, edges, weighted);
48778
+ }
48779
+ })
48780
+ });
48781
+ registerIBuiltin({
48782
+ name: "conncomp",
48783
+ help: {
48784
+ signatures: ["bins = conncomp(G)", "[bins, binsizes] = conncomp(G)"],
48785
+ description: "Connected components of an undirected graph G. Returns a row vector labeling each node with its component index, and optionally the size of each component."
48786
+ },
48787
+ resolve: () => ({
48788
+ outputTypes: [{ kind: "unknown" }, { kind: "unknown" }],
48789
+ apply: (args, nargout) => {
48790
+ const g = requireGraph(args[0], "conncomp");
48791
+ const n = graphN(g);
48792
+ const A = graphAdj(g);
48793
+ const bins = new Float64Array(n);
48794
+ const sizes = [];
48795
+ let comp = 0;
48796
+ const stack = [];
48797
+ for (let start = 0; start < n; start++) {
48798
+ if (bins[start] !== 0) continue;
48799
+ comp++;
48800
+ let count = 0;
48801
+ bins[start] = comp;
48802
+ stack.length = 0;
48803
+ stack.push(start);
48804
+ while (stack.length > 0) {
48805
+ const node = stack.pop();
48806
+ count++;
48807
+ eachNeighbor(A, node, (r) => {
48808
+ if (bins[r] === 0) {
48809
+ bins[r] = comp;
48810
+ stack.push(r);
48811
+ }
48812
+ });
48813
+ }
48814
+ sizes.push(count);
48815
+ }
48816
+ const binsT = RTV.tensor(bins, [1, n]);
48817
+ if (nargout >= 2) {
48818
+ return [binsT, RTV.tensor(allocFloat64Array(sizes), [1, sizes.length])];
48819
+ }
48820
+ return binsT;
48821
+ }
48822
+ })
48823
+ });
48824
+ registerIBuiltin({
48825
+ name: "laplacian",
48826
+ help: {
48827
+ signatures: ["L = laplacian(G)"],
48828
+ description: "Graph Laplacian matrix L = D - A of an undirected graph G, where A is the (binary, unweighted) adjacency matrix and D the diagonal node-degree matrix. Returns a sparse matrix."
48829
+ },
48830
+ resolve: () => ({
48831
+ outputTypes: [{ kind: "unknown" }],
48832
+ apply: (args) => {
48833
+ const g = requireGraph(args[0], "laplacian");
48834
+ const n = graphN(g);
48835
+ const A = graphAdj(g);
48836
+ const edges = [];
48837
+ for (let c = 0; c < n; c++) {
48838
+ let degree = 0;
48839
+ eachNeighbor(A, c, (r) => {
48840
+ degree++;
48841
+ if (r < c) edges.push({ u: r + 1, v: c + 1, w: -1 });
48842
+ });
48843
+ edges.push({ u: c + 1, v: c + 1, w: degree });
48844
+ }
48845
+ return buildSymAdj(n, edges);
48846
+ }
48847
+ })
48848
+ });
48849
+ registerIBuiltin({
48850
+ name: "addedge",
48851
+ help: {
48852
+ signatures: ["H = addedge(G, s, t)", "H = addedge(G, s, t, w)"],
48853
+ description: "Add one or more edges between nodes s and t to graph G, returning a new graph. Weights w are required when G is a weighted graph."
48854
+ },
48855
+ resolve: () => ({
48856
+ outputTypes: [
48857
+ {
48858
+ kind: "class_instance",
48859
+ className: "graph",
48860
+ isHandleClass: false,
48861
+ fields: {}
48862
+ }
48863
+ ],
48864
+ apply: (args) => {
48865
+ const g = requireGraph(args[0], "addedge");
48866
+ if (args.length < 3) {
48867
+ throw new RuntimeError("addedge: requires nodes s and t");
48868
+ }
48869
+ const weighted = graphWeighted(g);
48870
+ const s = toNumArray2(args[1]);
48871
+ const t = toNumArray2(args[2]);
48872
+ if (s.length !== t.length) {
48873
+ throw new RuntimeError("addedge: s and t must have the same length");
48874
+ }
48875
+ let w = null;
48876
+ if (args.length >= 4) {
48877
+ w = toNumArray2(args[3]);
48878
+ } else if (weighted) {
48879
+ throw new RuntimeError(
48880
+ "addedge: Must specify weights when adding an edge to a weighted graph."
48881
+ );
48882
+ }
48883
+ const edges = edgesFromAdj(graphAdj(g));
48884
+ let n = graphN(g);
48885
+ for (let i = 0; i < s.length; i++) {
48886
+ const u = Math.min(s[i], t[i]);
48887
+ const v = Math.max(s[i], t[i]);
48888
+ n = Math.max(n, u, v);
48889
+ edges.push({ u, v, w: w ? w[w.length === 1 ? 0 : i] : 1 });
48890
+ }
48891
+ return makeGraph(n, edges, weighted);
48892
+ }
48893
+ })
48894
+ });
48895
+ registerIBuiltin({
48896
+ name: "numnodes",
48897
+ help: {
48898
+ signatures: ["n = numnodes(G)"],
48899
+ description: "Number of nodes in graph G."
48900
+ },
48901
+ resolve: () => ({
48902
+ outputTypes: [{ kind: "number" }],
48903
+ apply: (args) => RTV.num(graphN(requireGraph(args[0], "numnodes")))
48904
+ })
48905
+ });
48906
+ registerIBuiltin({
48907
+ name: "numedges",
48908
+ help: {
48909
+ signatures: ["m = numedges(G)"],
48910
+ description: "Number of edges in graph G."
48911
+ },
48912
+ resolve: () => ({
48913
+ outputTypes: [{ kind: "number" }],
48914
+ apply: (args) => {
48915
+ const g = requireGraph(args[0], "numedges");
48916
+ return RTV.num(edgesFromAdj(graphAdj(g)).length);
48917
+ }
48918
+ })
48919
+ });
48920
+ registerIBuiltin({
48921
+ name: "degree",
48922
+ help: {
48923
+ signatures: ["d = degree(G)"],
48924
+ description: "Degree of each node in graph G, returned as a column vector of edge counts (self-loops excluded)."
48925
+ },
48926
+ resolve: () => ({
48927
+ outputTypes: [{ kind: "unknown" }],
48928
+ apply: (args) => {
48929
+ const g = requireGraph(args[0], "degree");
48930
+ const n = graphN(g);
48931
+ const A = graphAdj(g);
48932
+ const d = new Float64Array(n);
48933
+ for (let c = 0; c < n; c++) eachNeighbor(A, c, () => d[c] += 1);
48934
+ return RTV.tensor(d, [n, 1]);
48935
+ }
48936
+ })
48937
+ });
48938
+
48939
+ // src/numbl-core/interpreter/builtins/gallery.ts
48940
+ function toNumArray3(v, what) {
48941
+ if (isRuntimeNumber(v)) return [v];
48942
+ if (isRuntimeLogical(v)) return [v ? 1 : 0];
48943
+ if (isRuntimeTensor(v)) return Array.from(v.data);
48944
+ if (isRuntimeComplexNumber(v)) return [v.re];
48945
+ throw new RuntimeError(`gallery: ${what} must be numeric`);
48946
+ }
48947
+ function buildTridiag(sub2, diag2, sup) {
48948
+ const n = diag2.length;
48949
+ if (sub2.length !== n - 1 || sup.length !== n - 1) {
48950
+ throw new RuntimeError(
48951
+ "gallery: tridiag sub/superdiagonal must have length one less than the diagonal"
48952
+ );
48953
+ }
48954
+ const ir = [];
48955
+ const pr = [];
48956
+ const jc = new Int32Array(n + 1);
48957
+ for (let c = 0; c < n; c++) {
48958
+ jc[c] = ir.length;
48959
+ if (c >= 1 && sup[c - 1] !== 0) {
48960
+ ir.push(c - 1);
48961
+ pr.push(sup[c - 1]);
48962
+ }
48963
+ if (diag2[c] !== 0) {
48964
+ ir.push(c);
48965
+ pr.push(diag2[c]);
48966
+ }
48967
+ if (c <= n - 2 && sub2[c] !== 0) {
48968
+ ir.push(c + 1);
48969
+ pr.push(sub2[c]);
48970
+ }
48971
+ }
48972
+ jc[n] = ir.length;
48973
+ return RTV.sparseMatrix(n, n, new Int32Array(ir), jc, allocFloat64Array(pr));
48974
+ }
48975
+ function buildTridiagToeplitz(n, c, d, e) {
48976
+ const off = Math.max(0, n - 1);
48977
+ return buildTridiag(
48978
+ new Array(off).fill(c),
48979
+ new Array(n).fill(d),
48980
+ new Array(off).fill(e)
48981
+ );
48982
+ }
48983
+ function galleryTridiag(rest) {
48984
+ if (rest.length === 1) {
48985
+ return buildTridiagToeplitz(Math.round(toNumber(rest[0])), -1, 2, -1);
48986
+ }
48987
+ if (rest.length === 4) {
48988
+ return buildTridiagToeplitz(
48989
+ Math.round(toNumber(rest[0])),
48990
+ toNumber(rest[1]),
48991
+ toNumber(rest[2]),
48992
+ toNumber(rest[3])
48993
+ );
48994
+ }
48995
+ if (rest.length === 3) {
48996
+ const sub2 = toNumArray3(rest[0], "subdiagonal");
48997
+ const diag2 = toNumArray3(rest[1], "diagonal");
48998
+ const sup = toNumArray3(rest[2], "superdiagonal");
48999
+ if (sub2.length === 1 && diag2.length === 1 && sup.length === 1) {
49000
+ return buildTridiag([], diag2, []);
49001
+ }
49002
+ return buildTridiag(sub2, diag2, sup);
49003
+ }
49004
+ throw new RuntimeError("gallery: tridiag expects (n), (n,c,d,e), or (x,y,z)");
49005
+ }
49006
+ registerIBuiltin({
49007
+ name: "gallery",
49008
+ help: {
49009
+ signatures: [
49010
+ "A = gallery('tridiag', n)",
49011
+ "A = gallery('tridiag', n, c, d, e)",
49012
+ "A = gallery('tridiag', x, y, z)"
49013
+ ],
49014
+ description: "Test matrices. gallery('tridiag', ...) returns a sparse tridiagonal matrix: gallery('tridiag', n) is the order-n matrix with -1 on the sub/superdiagonals and 2 on the diagonal (the negative second-difference matrix); gallery('tridiag', n, c, d, e) is the order-n Toeplitz tridiagonal with scalar sub c, diagonal d, super e; gallery('tridiag', x, y, z) uses vectors x (subdiagonal), y (diagonal), z (superdiagonal), where x and z have length one less than y."
49015
+ },
49016
+ resolve: () => ({
49017
+ outputTypes: [{ kind: "unknown" }],
49018
+ apply: (args) => {
49019
+ if (args.length < 1) {
49020
+ throw new RuntimeError("gallery: not enough input arguments");
49021
+ }
49022
+ const name = parseStringArgLower(args[0]);
49023
+ let rest = args.slice(1);
49024
+ if (rest.length >= 1) {
49025
+ const last = rest[rest.length - 1];
49026
+ if (isRuntimeString(last) || isRuntimeChar(last)) {
49027
+ const cn = parseStringArgLower(last);
49028
+ if (cn === "single" || cn === "double") rest = rest.slice(0, -1);
49029
+ }
49030
+ }
49031
+ switch (name) {
49032
+ case "tridiag":
49033
+ return galleryTridiag(rest);
49034
+ default:
49035
+ throw new RuntimeError(
49036
+ `gallery: matrix family '${name}' is not supported`
49037
+ );
49038
+ }
49039
+ }
49040
+ })
49041
+ });
47547
49042
 
47548
49043
  // src/numbl-core/interpreter/builtins/special-math.ts
47549
49044
  function binaryApply(a, b, fn) {
@@ -48027,12 +49522,19 @@ registerIBuiltin({
48027
49522
 
48028
49523
  // src/numbl-core/native/geometry-bridge.ts
48029
49524
  var backend = null;
49525
+ var hullBackend = null;
48030
49526
  function setDelaunayBackend(fn) {
48031
49527
  backend = fn;
48032
49528
  }
48033
49529
  function getDelaunayBackend() {
48034
49530
  return backend;
48035
49531
  }
49532
+ function setConvexHullBackend(fn) {
49533
+ hullBackend = fn;
49534
+ }
49535
+ function getConvexHullBackend() {
49536
+ return hullBackend;
49537
+ }
48036
49538
 
48037
49539
  // src/numbl-core/interpreter/builtins/geometry.ts
48038
49540
  function toFlatArray(v, name) {
@@ -48085,7 +49587,7 @@ function simplexVolume(points, cell, dim) {
48085
49587
  for (let k = 2; k <= dim; k++) fact *= k;
48086
49588
  return Math.abs(det) / fact;
48087
49589
  }
48088
- function triangulateToTensor(points, dim) {
49590
+ function triangulateToTensor(points, dim, orientCCW = false) {
48089
49591
  const backend2 = getDelaunayBackend();
48090
49592
  if (!backend2)
48091
49593
  throw new RuntimeError(
@@ -48105,6 +49607,16 @@ function triangulateToTensor(points, dim) {
48105
49607
  const cells = volTol > 0 ? raw.filter((cell) => simplexVolume(points, cell, dim) > volTol) : raw;
48106
49608
  const numCells = cells.length;
48107
49609
  const cols = dim + 1;
49610
+ if (orientCCW && dim === 2) {
49611
+ for (const cell of cells) {
49612
+ const [a, b, c] = cell;
49613
+ const signedArea2 = (points[b][0] - points[a][0]) * (points[c][1] - points[a][1]) - (points[c][0] - points[a][0]) * (points[b][1] - points[a][1]);
49614
+ if (signedArea2 < 0) {
49615
+ cell[1] = c;
49616
+ cell[2] = b;
49617
+ }
49618
+ }
49619
+ }
48108
49620
  const out = allocFloat64Array(numCells * cols);
48109
49621
  for (let i = 0; i < numCells; i++) {
48110
49622
  const cell = cells[i];
@@ -48158,7 +49670,7 @@ defineBuiltin({
48158
49670
  throw new RuntimeError(
48159
49671
  `delaunay: need at least ${dim + 1} points for a ${dim}-D triangulation`
48160
49672
  );
48161
- return triangulateToTensor(points, dim);
49673
+ return triangulateToTensor(points, dim, true);
48162
49674
  }
48163
49675
  }
48164
49676
  ]
@@ -48201,6 +49713,203 @@ defineBuiltin({
48201
49713
  }
48202
49714
  ]
48203
49715
  });
49716
+ var CONVHULLN_MAX_DIM = 3;
49717
+ function convexHullFacets(points, dim, name) {
49718
+ const backend2 = getConvexHullBackend();
49719
+ if (!backend2)
49720
+ throw new RuntimeError(
49721
+ `${name}: convex-hull backend not initialized. In Node call loadQhullNodeBackend() (the CLI and library do this automatically); in the browser worker it loads on startup.`
49722
+ );
49723
+ return backend2(points, dim);
49724
+ }
49725
+ function hullInteriorPoint(points, facets, dim) {
49726
+ const used = /* @__PURE__ */ new Set();
49727
+ for (const f of facets) for (const idx of f) used.add(idx);
49728
+ const c = new Array(dim).fill(0);
49729
+ for (const idx of used) {
49730
+ const p2 = points[idx];
49731
+ for (let k = 0; k < dim; k++) c[k] += p2[k];
49732
+ }
49733
+ const m = used.size || 1;
49734
+ for (let k = 0; k < dim; k++) c[k] /= m;
49735
+ return c;
49736
+ }
49737
+ function hullMeasure(points, facets, dim) {
49738
+ const c = hullInteriorPoint(points, facets, dim);
49739
+ let total = 0;
49740
+ if (dim === 2) {
49741
+ for (const f of facets) {
49742
+ const a = points[f[0]];
49743
+ const b = points[f[1]];
49744
+ const ax = a[0] - c[0], ay = a[1] - c[1];
49745
+ const bx = b[0] - c[0], by = b[1] - c[1];
49746
+ total += Math.abs(ax * by - ay * bx) / 2;
49747
+ }
49748
+ } else {
49749
+ for (const f of facets) {
49750
+ const a = points[f[0]];
49751
+ const b = points[f[1]];
49752
+ const d = points[f[2]];
49753
+ const ax = a[0] - c[0], ay = a[1] - c[1], az = a[2] - c[2];
49754
+ const bx = b[0] - c[0], by = b[1] - c[1], bz = b[2] - c[2];
49755
+ const dx = d[0] - c[0], dy = d[1] - c[1], dz = d[2] - c[2];
49756
+ const det = ax * (by * dz - bz * dy) - ay * (bx * dz - bz * dx) + az * (bx * dy - by * dx);
49757
+ total += Math.abs(det) / 6;
49758
+ }
49759
+ }
49760
+ return total;
49761
+ }
49762
+ function facetsToTensor(facets, cols) {
49763
+ const n = facets.length;
49764
+ const out = allocFloat64Array(n * cols);
49765
+ for (let i = 0; i < n; i++) {
49766
+ const f = facets[i];
49767
+ for (let j = 0; j < cols; j++) out[j * n + i] = f[j] + 1;
49768
+ }
49769
+ return RTV.tensor(out, [n, cols]);
49770
+ }
49771
+ function orderHull2D(facets, points) {
49772
+ const adj = /* @__PURE__ */ new Map();
49773
+ for (const [a, b] of facets) {
49774
+ (adj.get(a) ?? adj.set(a, []).get(a)).push(b);
49775
+ (adj.get(b) ?? adj.set(b, []).get(b)).push(a);
49776
+ }
49777
+ const start = facets[0][0];
49778
+ const loop = [start];
49779
+ let prev = -1;
49780
+ let cur = start;
49781
+ for (let guard = 0; guard <= facets.length; guard++) {
49782
+ const nbrs = adj.get(cur);
49783
+ const next = nbrs[0] === prev ? nbrs[1] : nbrs[0];
49784
+ if (next === void 0 || next === start) break;
49785
+ loop.push(next);
49786
+ prev = cur;
49787
+ cur = next;
49788
+ }
49789
+ let signed = 0;
49790
+ for (let i = 0; i < loop.length; i++) {
49791
+ const p2 = points[loop[i]];
49792
+ const q = points[loop[(i + 1) % loop.length]];
49793
+ signed += p2[0] * q[1] - q[0] * p2[1];
49794
+ }
49795
+ if (signed < 0) loop.reverse();
49796
+ loop.push(loop[0]);
49797
+ const out = allocFloat64Array(loop.length);
49798
+ for (let i = 0; i < loop.length; i++) out[i] = loop[i] + 1;
49799
+ return RTV.tensor(out, [loop.length, 1]);
49800
+ }
49801
+ defineBuiltin({
49802
+ name: "convhull",
49803
+ help: {
49804
+ signatures: [
49805
+ "k = convhull(P)",
49806
+ "k = convhull(x,y)",
49807
+ "k = convhull(x,y,z)",
49808
+ "k = convhull(___,'Simplify',tf)",
49809
+ "[k,av] = convhull(___)"
49810
+ ],
49811
+ description: "Convex hull of a set of 2-D or 3-D points. With a single matrix P (N-by-2 or N-by-3), or coordinate vectors x,y (2-D) or x,y,z (3-D). For 2-D input, k is a column vector of 1-based point indices tracing the hull boundary counter-clockwise (closed: k(1)==k(end)). For 3-D input, k is a numFacets-by-3 matrix of triangle vertex indices. The second output av is the area (2-D) or volume (3-D). The 'Simplify' name-value pair is accepted for compatibility; the hull is always returned in minimal (simplified) form."
49812
+ },
49813
+ cases: [
49814
+ {
49815
+ match: (argTypes, nargout) => {
49816
+ if (nargout > 2) return null;
49817
+ if (argTypes.length < 1 || argTypes.length > 5) return null;
49818
+ const ok = (t) => t.kind === "number" || t.kind === "boolean" || t.kind === "tensor" || t.kind === "string" || t.kind === "char";
49819
+ if (!argTypes.every(ok)) return null;
49820
+ const k = { kind: "tensor", isComplex: false };
49821
+ return nargout > 1 ? [k, { kind: "number" }] : [k];
49822
+ },
49823
+ apply: (args, nargout) => {
49824
+ let coordArgs = args;
49825
+ const tail = args[args.length - 2];
49826
+ const isSimplifyName = (v) => typeof v === "string" && v.toLowerCase() === "simplify" || isRuntimeChar(v) && v.value.toLowerCase() === "simplify";
49827
+ if (args.length >= 3 && isSimplifyName(tail)) {
49828
+ coordArgs = args.slice(0, args.length - 2);
49829
+ }
49830
+ let points;
49831
+ let dim;
49832
+ if (coordArgs.length === 1) {
49833
+ const P = coordArgs[0];
49834
+ if (!isRuntimeTensor(P))
49835
+ throw new RuntimeError(
49836
+ "convhull: P must be an N-by-2 or N-by-3 matrix"
49837
+ );
49838
+ const [, d] = tensorSize2D(P);
49839
+ if (d !== 2 && d !== 3)
49840
+ throw new RuntimeError("convhull: P must have 2 or 3 columns");
49841
+ points = matrixToPoints(P, "convhull");
49842
+ dim = d;
49843
+ } else if (coordArgs.length === 2 || coordArgs.length === 3) {
49844
+ const coords = coordArgs.map((a) => toFlatArray(a, "convhull"));
49845
+ const n = coords[0].length;
49846
+ if (coords.some((c) => c.length !== n))
49847
+ throw new RuntimeError(
49848
+ "convhull: coordinate vectors must have the same length"
49849
+ );
49850
+ points = new Array(n);
49851
+ for (let i = 0; i < n; i++) points[i] = coords.map((c) => c[i]);
49852
+ dim = coordArgs.length;
49853
+ } else {
49854
+ throw new RuntimeError("convhull: invalid arguments");
49855
+ }
49856
+ if (points.length < dim + 1)
49857
+ throw new RuntimeError(
49858
+ `convhull: need at least ${dim + 1} points for a ${dim}-D hull`
49859
+ );
49860
+ const facets = convexHullFacets(points, dim, "convhull");
49861
+ const k = dim === 2 ? orderHull2D(facets, points) : facetsToTensor(facets, 3);
49862
+ if (nargout > 1) return [k, hullMeasure(points, facets, dim)];
49863
+ return k;
49864
+ }
49865
+ }
49866
+ ]
49867
+ });
49868
+ defineBuiltin({
49869
+ name: "convhulln",
49870
+ help: {
49871
+ signatures: [
49872
+ "k = convhulln(P)",
49873
+ "k = convhulln(P,opts)",
49874
+ "[k,vol] = convhulln(___)"
49875
+ ],
49876
+ description: "N-D convex hull. P is an m-by-n matrix of m points in n-dimensional space. Returns a numFacets-by-n matrix where each row holds the 1-based point indices of one simplicial facet (edges for n=2, triangles for n=3). The second output is the area (n=2) or volume (n=3). The opts argument (Qhull options) is accepted for MATLAB compatibility but ignored. Note: only n = 2 and n = 3 are supported (see source comments)."
49877
+ },
49878
+ cases: [
49879
+ {
49880
+ match: (argTypes, nargout) => {
49881
+ if (nargout > 2) return null;
49882
+ if (argTypes.length < 1 || argTypes.length > 2) return null;
49883
+ const x = argTypes[0];
49884
+ if (x.kind !== "tensor" && x.kind !== "number" && x.kind !== "boolean")
49885
+ return null;
49886
+ const k = { kind: "tensor", isComplex: false };
49887
+ return nargout > 1 ? [k, { kind: "number" }] : [k];
49888
+ },
49889
+ apply: (args, nargout) => {
49890
+ const P = args[0];
49891
+ if (!isRuntimeTensor(P))
49892
+ throw new RuntimeError("convhulln: P must be an m-by-n matrix");
49893
+ const [m, n] = tensorSize2D(P);
49894
+ if (n < 2)
49895
+ throw new RuntimeError("convhulln: P must have at least 2 columns");
49896
+ if (n > CONVHULLN_MAX_DIM)
49897
+ throw new RuntimeError(
49898
+ `convhulln: only 2-D and 3-D hulls are supported (got ${n}-D); higher dimensions are not yet validated`
49899
+ );
49900
+ if (m < n + 1)
49901
+ throw new RuntimeError(
49902
+ `convhulln: need at least ${n + 1} points for a ${n}-D hull`
49903
+ );
49904
+ const points = matrixToPoints(P, "convhulln");
49905
+ const facets = convexHullFacets(points, n, "convhulln");
49906
+ const k = facetsToTensor(facets, n);
49907
+ if (nargout > 1) return [k, hullMeasure(points, facets, n)];
49908
+ return k;
49909
+ }
49910
+ }
49911
+ ]
49912
+ });
48204
49913
  function getQueryValues(v, name) {
48205
49914
  if (typeof v === "number") return { data: [v], shape: null };
48206
49915
  if (typeof v === "boolean") return { data: [v ? 1 : 0], shape: null };
@@ -48698,6 +50407,13 @@ registerIBuiltin({
48698
50407
  apply: (_args, nargout) => nargout >= 1 ? RTV.dummyHandle() : void 0
48699
50408
  })
48700
50409
  });
50410
+ registerIBuiltin({
50411
+ name: "waitbar",
50412
+ resolve: () => ({
50413
+ outputTypes: [{ kind: "unknown" }],
50414
+ apply: (_args, nargout) => nargout >= 1 ? RTV.dummyHandle() : void 0
50415
+ })
50416
+ });
48701
50417
  registerIBuiltin({
48702
50418
  name: "get",
48703
50419
  resolve: () => ({
@@ -49836,6 +51552,10 @@ var H = {
49836
51552
  signatures: ["C = class(A)"],
49837
51553
  description: "Returns the class name of A as a character vector."
49838
51554
  },
51555
+ superclasses: {
51556
+ signatures: ["S = superclasses(ClassName)", "S = superclasses(obj)"],
51557
+ description: "Names of the superclasses of a class, returned as a column cell array of character vectors. Accepts a class name (char/string) or an object. Returns an empty cell for built-in types or classes with no superclasses."
51558
+ },
49839
51559
  fieldnames: {
49840
51560
  signatures: ["F = fieldnames(S)"],
49841
51561
  description: "Returns cell array of field names of struct or object S."
@@ -50239,6 +51959,14 @@ var H = {
50239
51959
  signatures: ["B = pinv(A)", "B = pinv(A, TOL)"],
50240
51960
  description: "Moore-Penrose pseudoinverse."
50241
51961
  },
51962
+ null: {
51963
+ signatures: ["Z = null(A)", "Z = null(A, TOL)"],
51964
+ description: "Orthonormal basis for the null space of A (columns of Z), computed via the SVD. TOL overrides the singular-value tolerance."
51965
+ },
51966
+ bandwidth: {
51967
+ signatures: ["[lower, upper] = bandwidth(A)", "B = bandwidth(A, TYPE)"],
51968
+ description: "Lower and upper matrix bandwidth. TYPE is 'lower' or 'upper'; a single output without TYPE returns the lower bandwidth."
51969
+ },
50242
51970
  cond: {
50243
51971
  signatures: ["C = cond(A)", "C = cond(A, P)"],
50244
51972
  description: "Condition number of matrix."
@@ -50468,6 +52196,10 @@ var H = {
50468
52196
  signatures: ["Y = double(X)"],
50469
52197
  description: "Convert to double precision."
50470
52198
  },
52199
+ single: {
52200
+ signatures: ["Y = single(X)"],
52201
+ description: "Round to single precision. numbl stores all numerics as double, so the result reports class 'double', but the low-order bits are dropped as in MATLAB."
52202
+ },
50471
52203
  logical: {
50472
52204
  signatures: ["Y = logical(X)"],
50473
52205
  description: "Convert to logical (boolean) array."
@@ -50838,6 +52570,18 @@ var H = {
50838
52570
  ],
50839
52571
  description: "Extract or create sparse banded/diagonal matrices."
50840
52572
  },
52573
+ spparms: {
52574
+ signatures: [
52575
+ "spparms",
52576
+ "values = spparms",
52577
+ "[keys, values] = spparms",
52578
+ "value = spparms(KEY)",
52579
+ "spparms(KEY, VALUE)",
52580
+ "spparms('default')",
52581
+ "spparms(values)"
52582
+ ],
52583
+ description: "Get/set sparse direct-solver parameters. numbl's sparse solver ignores these knobs; values are stored so they round-trip through get/set/restore but have no effect."
52584
+ },
50841
52585
  // ── Linear algebra extras ─────────────────────────────────────────────
50842
52586
  qz: {
50843
52587
  signatures: [
@@ -52459,7 +54203,9 @@ function dispatchPlotBuiltin(name, args, instructions, state) {
52459
54203
  return true;
52460
54204
  }
52461
54205
  case "close": {
52462
- if (args.length > 0 && toString(args[0]) === "all") {
54206
+ const a0 = args[0];
54207
+ const isText3 = a0 !== void 0 && (isRuntimeChar(a0) || isRuntimeString(a0));
54208
+ if (isText3 && toString(a0) === "all") {
52463
54209
  plotInstr(instructions, { type: "close_all" });
52464
54210
  } else {
52465
54211
  plotInstr(instructions, { type: "close" });
@@ -53386,6 +55132,8 @@ var SPECIAL_BUILTIN_NAMES = [
53386
55132
  "delete",
53387
55133
  "rmdir",
53388
55134
  "movefile",
55135
+ "copyfile",
55136
+ "fileattrib",
53389
55137
  "unzip",
53390
55138
  "dir",
53391
55139
  "warning",
@@ -53395,6 +55143,7 @@ var SPECIAL_BUILTIN_NAMES = [
53395
55143
  "userpath",
53396
55144
  "getenv",
53397
55145
  "setenv",
55146
+ "maxNumCompThreads",
53398
55147
  "pwd",
53399
55148
  "cd",
53400
55149
  "ode45",
@@ -53502,8 +55251,8 @@ function registerSpecialBuiltins(rt) {
53502
55251
  if (args.length === 0) return nargout >= 1 ? RTV.num(0) : void 0;
53503
55252
  const margs = args.map((a) => ensureRuntimeValue(a));
53504
55253
  if (margs.length === 2 && isRuntimeChar(margs[0]) && isRuntimeChar(margs[1])) {
53505
- const state = toString(margs[0]);
53506
- if (state === "on" || state === "off") {
55254
+ const mode = toString(margs[0]);
55255
+ if (mode === "on" || mode === "off") {
53507
55256
  if (nargout === 0) return void 0;
53508
55257
  return RTV.struct(
53509
55258
  /* @__PURE__ */ new Map([
@@ -53512,6 +55261,14 @@ function registerSpecialBuiltins(rt) {
53512
55261
  ])
53513
55262
  );
53514
55263
  }
55264
+ if (mode === "query") {
55265
+ return RTV.struct(
55266
+ /* @__PURE__ */ new Map([
55267
+ ["state", RTV.char("on")],
55268
+ ["identifier", margs[1]]
55269
+ ])
55270
+ );
55271
+ }
53515
55272
  }
53516
55273
  let fmtIdx = 0;
53517
55274
  if (margs.length >= 2 && isRuntimeChar(margs[0]) && toString(margs[0]).includes(":")) {
@@ -54328,6 +56085,65 @@ function registerSpecialBuiltins(rt) {
54328
56085
  RTV.char("")
54329
56086
  ];
54330
56087
  });
56088
+ registerSpecial("copyfile", (nargout, args) => {
56089
+ const io = requireFileIO();
56090
+ if (!io.copyfile)
56091
+ throw new RuntimeError("copyfile is not available in this environment");
56092
+ const margs = args.map((a) => ensureRuntimeValue(a));
56093
+ if (margs.length < 1)
56094
+ throw new RuntimeError("copyfile requires at least 1 argument");
56095
+ const source = toString(margs[0]);
56096
+ const destination = margs.length >= 2 ? toString(margs[1]) : rt.system?.cwd() ?? ".";
56097
+ let force = false;
56098
+ if (margs.length >= 3) {
56099
+ const third = toString(margs[2]);
56100
+ if (third.toLowerCase() === "f") force = true;
56101
+ }
56102
+ const ok = io.copyfile(source, destination, force);
56103
+ if (nargout === 0) {
56104
+ if (!ok)
56105
+ throw new RuntimeError(
56106
+ `copyfile: cannot copy '${source}' to '${destination}'`
56107
+ );
56108
+ return void 0;
56109
+ }
56110
+ return nargout <= 1 ? RTV.num(ok ? 1 : 0) : [
56111
+ RTV.num(ok ? 1 : 0),
56112
+ RTV.char(ok ? "" : `Cannot copy '${source}' to '${destination}'`),
56113
+ RTV.char("")
56114
+ ];
56115
+ });
56116
+ registerSpecial("fileattrib", (nargout, args) => {
56117
+ const io = requireFileIO();
56118
+ if (!io.fileattrib)
56119
+ throw new RuntimeError("fileattrib is not available in this environment");
56120
+ const margs = args.map((a) => ensureRuntimeValue(a));
56121
+ if (margs.length < 1)
56122
+ throw new RuntimeError("fileattrib requires at least 1 argument");
56123
+ const p2 = toString(margs[0]);
56124
+ const info = io.fileattrib(p2);
56125
+ if (nargout === 0) {
56126
+ if (!info)
56127
+ throw new RuntimeError(
56128
+ `fileattrib: cannot find '${p2}': No such file or directory`
56129
+ );
56130
+ return void 0;
56131
+ }
56132
+ const values = info ? RTV.struct(
56133
+ /* @__PURE__ */ new Map([
56134
+ ["Name", RTV.char(info.Name)],
56135
+ ["archive", RTV.num(0)],
56136
+ ["system", RTV.num(0)],
56137
+ ["hidden", RTV.num(0)],
56138
+ ["directory", RTV.num(info.directory ? 1 : 0)],
56139
+ ["UserRead", RTV.num(info.UserRead ? 1 : 0)],
56140
+ ["UserWrite", RTV.num(info.UserWrite ? 1 : 0)],
56141
+ ["UserExecute", RTV.num(info.UserExecute ? 1 : 0)]
56142
+ ])
56143
+ ) : RTV.char(`No such file or directory: ${p2}`);
56144
+ if (nargout <= 1) return RTV.num(info ? 1 : 0);
56145
+ return [RTV.num(info ? 1 : 0), values, RTV.char("")];
56146
+ });
54331
56147
  registerSpecial("unzip", (nargout, args) => {
54332
56148
  const io = requireFileIO();
54333
56149
  if (!io.unzip)
@@ -54653,6 +56469,7 @@ function registerSpecialBuiltins(rt) {
54653
56469
  }
54654
56470
  return RTV.char(sys?.getEnv(toString(args[0])) ?? "");
54655
56471
  });
56472
+ registerSpecial("maxNumCompThreads", () => RTV.num(1));
54656
56473
  registerSpecialVoid("setenv", (args) => {
54657
56474
  const sys = rt.system;
54658
56475
  if (args.length === 2) {
@@ -55034,6 +56851,10 @@ function _eigsImpl(rt, nargout, args) {
55034
56851
  const margs = args.map((a) => ensureRuntimeValue(a));
55035
56852
  if (margs.length < 1)
55036
56853
  throw new RuntimeError("eigs requires at least 1 argument");
56854
+ for (let i = 0; i < margs.length; i++) {
56855
+ const mi = margs[i];
56856
+ if (isRuntimeSparseMatrix(mi)) margs[i] = sparseToDense(mi);
56857
+ }
55037
56858
  let afun = null;
55038
56859
  let A = null;
55039
56860
  let n;
@@ -56258,43 +58079,40 @@ function methodDispatch(rt, name, nargout, args) {
56258
58079
  }
56259
58080
  }
56260
58081
  }
56261
- try {
56262
- return callClassMethod(rt, firstRV.className, name, nargout, args);
56263
- } catch (e) {
56264
- if (e instanceof RuntimeError) {
56265
- const guardKey = `${firstRV.className}.subsref`;
56266
- if (!rt.activeAccessors.has(guardKey)) {
56267
- const subsrefFn = rt.cachedResolveClassMethod(
56268
- firstRV.className,
56269
- "subsref"
56270
- );
56271
- if (subsrefFn) {
56272
- const remaining = args.slice(1);
56273
- const sEntries = [
56274
- RTV.struct({
56275
- type: RTV.char("."),
56276
- subs: RTV.char(name)
56277
- }),
56278
- RTV.struct({
56279
- type: RTV.char("()"),
56280
- subs: RTV.cell(
56281
- remaining.map((a) => ensureRuntimeValue(a)),
56282
- [1, remaining.length]
56283
- )
56284
- })
56285
- ];
56286
- const S = RTV.structArray(["type", "subs"], sEntries);
56287
- rt.activeAccessors.add(guardKey);
56288
- try {
56289
- return subsrefFn(nargout, first, S);
56290
- } finally {
56291
- rt.activeAccessors.delete(guardKey);
56292
- }
58082
+ const methodExists = rt.cachedResolveClassMethod(firstRV.className, name) !== null;
58083
+ if (!methodExists) {
58084
+ const guardKey = `${firstRV.className}.subsref`;
58085
+ if (!rt.activeAccessors.has(guardKey)) {
58086
+ const subsrefFn = rt.cachedResolveClassMethod(
58087
+ firstRV.className,
58088
+ "subsref"
58089
+ );
58090
+ if (subsrefFn) {
58091
+ const remaining = args.slice(1);
58092
+ const sEntries = [
58093
+ RTV.struct({
58094
+ type: RTV.char("."),
58095
+ subs: RTV.char(name)
58096
+ }),
58097
+ RTV.struct({
58098
+ type: RTV.char("()"),
58099
+ subs: RTV.cell(
58100
+ remaining.map((a) => ensureRuntimeValue(a)),
58101
+ [1, remaining.length]
58102
+ )
58103
+ })
58104
+ ];
58105
+ const S = RTV.structArray(["type", "subs"], sEntries);
58106
+ rt.activeAccessors.add(guardKey);
58107
+ try {
58108
+ return subsrefFn(nargout, first, S);
58109
+ } finally {
58110
+ rt.activeAccessors.delete(guardKey);
56293
58111
  }
56294
58112
  }
56295
58113
  }
56296
- throw e;
56297
58114
  }
58115
+ return callClassMethod(rt, firstRV.className, name, nargout, args);
56298
58116
  }
56299
58117
  }
56300
58118
  const builtin = rt.builtins[name];
@@ -56351,11 +58169,13 @@ function arrayfunCellfunImpl(rt, name, nargout, args) {
56351
58169
  const getElem = (arr, i) => {
56352
58170
  if (isRuntimeCell(arr)) return arr.data[i];
56353
58171
  if (isRuntimeTensor(arr)) return arr.data[i];
58172
+ if (isRuntimeStructArray(arr)) return arr.elements[i];
56354
58173
  return arr;
56355
58174
  };
56356
58175
  const getLen = (arr) => {
56357
58176
  if (isRuntimeCell(arr)) return arr.data.length;
56358
58177
  if (isRuntimeTensor(arr)) return arr.data.length;
58178
+ if (isRuntimeStructArray(arr)) return arr.elements.length;
56359
58179
  return 1;
56360
58180
  };
56361
58181
  const collectArgs = (i) => {
@@ -56391,9 +58211,9 @@ function arrayfunCellfunImpl(rt, name, nargout, args) {
56391
58211
  if (allLogical && arrArg.data.length > 0) result._isLogical = true;
56392
58212
  return result;
56393
58213
  }
56394
- if (isRuntimeCell(arrArg) || extraInputs.length > 0 || nargout > 1) {
58214
+ if (isRuntimeCell(arrArg) || isRuntimeStructArray(arrArg) || extraInputs.length > 0 || nargout > 1) {
56395
58215
  const len = getLen(arrArg);
56396
- const shape = isRuntimeCell(arrArg) ? [...arrArg.shape] : isRuntimeTensor(arrArg) ? [...arrArg.shape] : [1, len];
58216
+ const shape = isRuntimeCell(arrArg) || isRuntimeTensor(arrArg) ? [...arrArg.shape] : [1, len];
56397
58217
  if (nargout > 1) {
56398
58218
  const allResults = Array.from(
56399
58219
  { length: nargout },
@@ -57228,6 +59048,28 @@ function indexStore(rt, base, indices, rhs, skipSubsasgn = false) {
57228
59048
  if (isRuntimeStruct(mv)) {
57229
59049
  return ensureRuntimeValue(rhs);
57230
59050
  }
59051
+ if (indices.length === 1 && isRuntimeClassInstance(ensureRuntimeValue(rhs)) && // Object-array growth uses a numeric position. A char/string index (e.g. a
59052
+ // key into a containers.Map whose value happens to be a class instance)
59053
+ // must fall through to the subsasgn dispatch below, not be coerced to a
59054
+ // number — `toNumber` on a multi-char value throws.
59055
+ !isRuntimeChar(ensureRuntimeValue(indices[0]))) {
59056
+ const k = Math.round(toNumber(ensureRuntimeValue(indices[0]))) - 1;
59057
+ const growsArray = isRuntimeClassInstanceArray(mv) || isRuntimeTensor(mv) && mv.data.length === 0 || isRuntimeClassInstance(mv) && k >= 1;
59058
+ if (growsArray) {
59059
+ if (k < 0) throw new RuntimeError("Index must be a positive integer");
59060
+ const rhsInst = ensureRuntimeValue(rhs);
59061
+ const existing = isRuntimeClassInstanceArray(mv) ? [...mv.elements] : isRuntimeClassInstance(mv) ? [mv] : [];
59062
+ const className = isRuntimeClassInstanceArray(mv) ? mv.className : isRuntimeClassInstance(mv) ? mv.className : rhsInst.className;
59063
+ while (existing.length < k) {
59064
+ existing.push(
59065
+ RTV.classInstance(className, [...rhsInst.fields.keys()], false)
59066
+ );
59067
+ }
59068
+ existing[k] = rhsInst;
59069
+ if (existing.length === 1) return existing[0];
59070
+ return RTV.classInstanceArray(className, existing, [1, existing.length]);
59071
+ }
59072
+ }
57231
59073
  if (isRuntimeClassInstance(mv)) {
57232
59074
  const guardKey = `${mv.className}.subsasgn`;
57233
59075
  if (!skipSubsasgn && !rt.activeAccessors.has(guardKey)) {
@@ -57385,12 +59227,17 @@ function getMember(rt, base, name) {
57385
59227
  `No property or method '${name}' for class '${mv.className}'`
57386
59228
  );
57387
59229
  }
59230
+ if (isRuntimeClassInstanceArray(mv)) {
59231
+ const values = mv.elements.map(
59232
+ (el) => ensureRuntimeValue(getMember(rt, el, name))
59233
+ );
59234
+ return horzcat(...values);
59235
+ }
57388
59236
  return getRTValueField(mv, name);
57389
59237
  }
57390
- function getMemberDynamic(base, nameExpr) {
57391
- const mv = ensureRuntimeValue(base);
59238
+ function getMemberDynamic(rt, base, nameExpr) {
57392
59239
  const name = toString(ensureRuntimeValue(nameExpr));
57393
- return getRTValueField(mv, name);
59240
+ return ensureRuntimeValue(getMember(rt, base, name));
57394
59241
  }
57395
59242
  function getMemberOrEmpty(base, name) {
57396
59243
  try {
@@ -57412,7 +59259,7 @@ function setMemberReturn(rt, base, name, rhs) {
57412
59259
  if (setter) {
57413
59260
  rt.activeAccessors.add(accessorKey);
57414
59261
  try {
57415
- const result = setter(1, base, rhs);
59262
+ const result = setter(0, base, rhs);
57416
59263
  return result !== void 0 ? result : base;
57417
59264
  } finally {
57418
59265
  rt.activeAccessors.delete(accessorKey);
@@ -57424,10 +59271,8 @@ function setMemberReturn(rt, base, name, rhs) {
57424
59271
  return setRTValueField(mv, name, rhsMv, rt);
57425
59272
  }
57426
59273
  function setMemberDynamicReturn(rt, base, nameExpr, rhs) {
57427
- const mv = ensureRuntimeValue(base);
57428
59274
  const name = toString(ensureRuntimeValue(nameExpr));
57429
- const rhsMv = ensureRuntimeValue(rhs);
57430
- return setRTValueField(mv, name, rhsMv, rt);
59275
+ return ensureRuntimeValue(setMemberReturn(rt, base, name, rhs));
57431
59276
  }
57432
59277
  function subsrefCall(rt, base, names) {
57433
59278
  const mv = ensureRuntimeValue(base);
@@ -58722,7 +60567,7 @@ function marchingCubes(dims, getVal, getCoord, iso, opts) {
58722
60567
  }
58723
60568
  return { vertices, faces, colors };
58724
60569
  }
58725
- function isNumericArg2(v) {
60570
+ function isNumericArg3(v) {
58726
60571
  return typeof v === "number" || typeof v === "boolean" || isRuntimeTensor(v);
58727
60572
  }
58728
60573
  function dims3(v) {
@@ -58739,7 +60584,7 @@ function isosurfaceFromArgs(args) {
58739
60584
  if (isRuntimeChar(a) || isRuntimeString(a)) {
58740
60585
  const s = toString(a).toLowerCase();
58741
60586
  if (s === "noshare") share = false;
58742
- } else if (isNumericArg2(a)) {
60587
+ } else if (isNumericArg3(a)) {
58743
60588
  nums.push(a);
58744
60589
  }
58745
60590
  }
@@ -60076,7 +61921,7 @@ var Runtime = class _Runtime {
60076
61921
  return getMember(this, base, name);
60077
61922
  }
60078
61923
  getMemberDynamic(base, nameExpr) {
60079
- return getMemberDynamic(base, nameExpr);
61924
+ return getMemberDynamic(this, base, nameExpr);
60080
61925
  }
60081
61926
  getMemberOrEmpty(base, name) {
60082
61927
  return getMemberOrEmpty(base, name);
@@ -60274,6 +62119,13 @@ function defaultClassInstanceVertcat(rows) {
60274
62119
  }
60275
62120
 
60276
62121
  // src/numbl-core/jsUserFunctions.ts
62122
+ function callHandle(handle, args, nargout = 1) {
62123
+ const rt = getCurrentRuntime();
62124
+ if (!rt) {
62125
+ throw new RuntimeError("callHandle: no active runtime to invoke handle");
62126
+ }
62127
+ return rt.index(handle, args, nargout);
62128
+ }
60277
62129
  function funcNameFromFile(fileName) {
60278
62130
  const base = fileName.split("/").pop();
60279
62131
  return base.replace(/\.numbl\.js$/, "");
@@ -60319,6 +62171,8 @@ function instantiateWasm(wasmData) {
60319
62171
  const moduleImports = WebAssembly.Module.imports(wasmModule);
60320
62172
  const importObject = {};
60321
62173
  const neededModules = new Set(moduleImports.map((i) => i.module));
62174
+ const callbacks = /* @__PURE__ */ new Map();
62175
+ let nextCbId = 1;
60322
62176
  if (neededModules.has("wasi_snapshot_preview1")) {
60323
62177
  importObject.wasi_snapshot_preview1 = {
60324
62178
  fd_write: () => 0,
@@ -60338,10 +62192,36 @@ function instantiateWasm(wasmData) {
60338
62192
  if (neededModules.has("env")) {
60339
62193
  importObject.env = {
60340
62194
  emscripten_notify_memory_growth: () => {
62195
+ },
62196
+ // Scalar callback: WASM calls back into a registered handle with one
62197
+ // f64 and receives an f64. Exceptions thrown here (incl. a missing id)
62198
+ // propagate out through the WASM call into the apply.
62199
+ numbl_cb_d: (id, x) => {
62200
+ const fn = callbacks.get(id);
62201
+ if (!fn) {
62202
+ throw new RuntimeError(
62203
+ `numbl_cb_d: no callback registered for id ${id}`
62204
+ );
62205
+ }
62206
+ return fn(x);
60341
62207
  }
60342
62208
  };
60343
62209
  }
60344
- return new WebAssembly.Instance(wasmModule, importObject);
62210
+ const instance = new WebAssembly.Instance(
62211
+ wasmModule,
62212
+ importObject
62213
+ );
62214
+ instance.callbacks = {
62215
+ add(fn) {
62216
+ const id = nextCbId++;
62217
+ callbacks.set(id, fn);
62218
+ return id;
62219
+ },
62220
+ remove(id) {
62221
+ callbacks.delete(id);
62222
+ }
62223
+ };
62224
+ return instance;
60345
62225
  }
60346
62226
  function resolveBindings(file, directives, getWasmInstance, nativeBridge) {
60347
62227
  const wasmInstance = directives.wasm ? getWasmInstance(directives.wasm) : void 0;
@@ -60416,6 +62296,8 @@ function loadJsUserFunctions(jsFiles, wasmFiles, nativeBridge) {
60416
62296
  "wasm",
60417
62297
  "native",
60418
62298
  "importJS",
62299
+ "callHandle",
62300
+ "toNumber",
60419
62301
  libFile.source
60420
62302
  );
60421
62303
  const exports = factory(
@@ -60425,7 +62307,9 @@ function loadJsUserFunctions(jsFiles, wasmFiles, nativeBridge) {
60425
62307
  dummyRegister,
60426
62308
  wasmInstance,
60427
62309
  nativeLib,
60428
- importJS
62310
+ importJS,
62311
+ callHandle,
62312
+ toNumber
60429
62313
  );
60430
62314
  libCache.set(name, exports);
60431
62315
  return exports;
@@ -60463,6 +62347,8 @@ function loadJsUserFunctions(jsFiles, wasmFiles, nativeBridge) {
60463
62347
  "wasm",
60464
62348
  "native",
60465
62349
  "importJS",
62350
+ "callHandle",
62351
+ "toNumber",
60466
62352
  file.source
60467
62353
  );
60468
62354
  factory(
@@ -60472,7 +62358,9 @@ function loadJsUserFunctions(jsFiles, wasmFiles, nativeBridge) {
60472
62358
  registerFn,
60473
62359
  wasmInstance,
60474
62360
  nativeLib,
60475
- importJS
62361
+ importJS,
62362
+ callHandle,
62363
+ toNumber
60476
62364
  );
60477
62365
  if (!builtin) {
60478
62366
  throw new Error(
@@ -60724,6 +62612,7 @@ function resolveFunctionImpl(name, argTypes, callSite, index2) {
60724
62612
  for (const argType of argTypes) {
60725
62613
  if (argType?.kind === "ClassInstance") {
60726
62614
  const className = argType.className;
62615
+ if (index2.classConstructors.get(className) === name) continue;
60727
62616
  if (!candidates.includes(className) && (index2.classInstanceMethods.get(className)?.has(name) || index2.classStaticMethods.get(className)?.has(name))) {
60728
62617
  candidates.push(className);
60729
62618
  }
@@ -60832,12 +62721,25 @@ var Environment = class _Environment {
60832
62721
  }
60833
62722
  /** Function ID for persistent variable storage */
60834
62723
  persistentFuncId;
62724
+ /** Call-site variable names of this frame's arguments, for `inputname`.
62725
+ * Entry i is the name of the variable passed as argument i+1, or '' if
62726
+ * that argument was not a plain variable. Undefined when the call did
62727
+ * not originate from an interpreted call expression (e.g. feval, JIT).
62728
+ * Read directly off the executing frame — the interpreter has only
62729
+ * function-level scoping, so `this.env` is the frame while a body runs. */
62730
+ inputArgNames;
60835
62731
  /** Back-reference to the runtime (needed for global/persistent access) */
60836
62732
  rt = null;
60837
62733
  /** Set when a `@nestedFn` handle has been created that captures this env
60838
62734
  * (or an ancestor). Tells the function-exit cleanup that clearing this
60839
62735
  * env would strand the handle's closure, so locals must be left alive. */
60840
62736
  nestedHandleCreated = false;
62737
+ /** For a nested-function frame: the names of this function's own formal
62738
+ * input/output arguments. These are always local — a write must never be
62739
+ * redirected to a same-named variable in the parent, even before the
62740
+ * output has been assigned. (MATLAB scopes a nested function's formal
62741
+ * arguments to that function; only other variables are shared.) */
62742
+ nestedLocalNames;
60841
62743
  get(name) {
60842
62744
  if (this._globalNames !== void 0 && this._globalNames.has(name) && this.rt) {
60843
62745
  const v = this.rt.$g[name];
@@ -60856,7 +62758,7 @@ var Environment = class _Environment {
60856
62758
  if (old !== void 0) decref(this.rt, old);
60857
62759
  return;
60858
62760
  }
60859
- if (this.isNested && !this.vars.has(name) && this.parent) {
62761
+ if (this.isNested && !this.vars.has(name) && this.parent && !this.nestedLocalNames?.has(name)) {
60860
62762
  const owner = this.findOwner(name);
60861
62763
  if (owner) {
60862
62764
  owner.setLocal(name, value);
@@ -62051,6 +63953,7 @@ function makeRootContext(interp, registry3) {
62051
63953
  var interpreterExec_exports = {};
62052
63954
  __export(interpreterExec_exports, {
62053
63955
  assignLValue: () => assignLValue,
63956
+ computeInputNames: () => computeInputNames,
62054
63957
  evalAnonFunc: () => evalAnonFunc,
62055
63958
  evalArgs: () => evalArgs,
62056
63959
  evalBinary: () => evalBinary,
@@ -62077,6 +63980,15 @@ __export(interpreterExec_exports, {
62077
63980
  writeLValueBase: () => writeLValueBase
62078
63981
  });
62079
63982
 
63983
+ // src/numbl-core/jitDeclineDiagnostics.ts
63984
+ var lastDecline = null;
63985
+ function recordJitDecline(d) {
63986
+ lastDecline = d;
63987
+ }
63988
+ function getLastJitDecline() {
63989
+ return lastDecline;
63990
+ }
63991
+
62080
63992
  // src/numbl-core/runtime/cow.ts
62081
63993
  function cowCopy(v) {
62082
63994
  if (isRuntimeTensor(v)) {
@@ -62353,6 +64265,11 @@ function execStmtInner(stmt) {
62353
64265
  case "Global": {
62354
64266
  for (const name of stmt.names) {
62355
64267
  this.env.globalNames.add(name);
64268
+ if (this.rt && !(name in this.rt.$g)) {
64269
+ const empty = RTV.tensor(allocFloat64Array(0), [0, 0]);
64270
+ incref(empty);
64271
+ this.rt.$g[name] = empty;
64272
+ }
62356
64273
  }
62357
64274
  return null;
62358
64275
  }
@@ -62374,14 +64291,16 @@ function execStmtInner(stmt) {
62374
64291
  case "Directive": {
62375
64292
  if (stmt.directive === "assert_jit") {
62376
64293
  const wantC = stmt.args.includes("c");
64294
+ const decline = getLastJitDecline();
64295
+ const why = decline ? ` Most recent JIT decline (${decline.where}, ${decline.kind}): ${decline.message}` : ` (no JIT decline reason was recorded \u2014 the unit may have been declined before lowering, e.g. an unsupported input type.)`;
62377
64296
  if (this.optimization === "1") {
62378
64297
  throw new RuntimeError(
62379
- `%!numbl:assert_jit: expected the enclosing loop/function/script to be JS-JIT-compiled at --opt 1, but it ran in the interpreter. (Run with --opt 0 to silence.)`
64298
+ `%!numbl:assert_jit: expected the enclosing loop/function/script to be JS-JIT-compiled at --opt 1, but it ran in the interpreter.${why} (Run with --opt 0 to silence.)`
62380
64299
  );
62381
64300
  }
62382
64301
  if (this.optimization === "2" && wantC) {
62383
64302
  throw new RuntimeError(
62384
- `%!numbl:assert_jit c: expected the enclosing loop/function/script to be C-JIT-compiled at --opt 2, but it ran in the interpreter. (Run with --opt 0 to silence.)`
64303
+ `%!numbl:assert_jit c: expected the enclosing loop/function/script to be C-JIT-compiled at --opt 2, but it ran in the interpreter.${why} (Run with --opt 0 to silence.)`
62385
64304
  );
62386
64305
  }
62387
64306
  }
@@ -62464,6 +64383,10 @@ function evalExprNargout(expr, nargout) {
62464
64383
  if (isRuntimeStructArray(rv)) {
62465
64384
  return rv.elements.length;
62466
64385
  }
64386
+ if (isRuntimeClassInstanceArray(rv)) {
64387
+ if (ctx.numIndices === 1) return rv.elements.length;
64388
+ return ctx.dimIndex < rv.shape.length ? rv.shape[ctx.dimIndex] : 1;
64389
+ }
62467
64390
  if (isRuntimeSparseMatrix(rv)) {
62468
64391
  if (ctx.numIndices === 1) return rv.m * rv.n;
62469
64392
  return ctx.dimIndex === 0 ? rv.m : ctx.dimIndex === 1 ? rv.n : 1;
@@ -62624,6 +64547,23 @@ function evalExprNargout(expr, nargout) {
62624
64547
  throw new RuntimeError("Interpreter does not yet support meta.class");
62625
64548
  }
62626
64549
  }
64550
+ function computeInputNames(argExprs, callerEnv) {
64551
+ const names = [];
64552
+ let blanked = false;
64553
+ for (const a of argExprs) {
64554
+ if (blanked) {
64555
+ names.push("");
64556
+ } else if (a.type === "Ident" && callerEnv.has(a.name)) {
64557
+ names.push(a.name);
64558
+ } else if (a.type === "IndexCell" || a.type === "Member" || a.type === "MemberDynamic") {
64559
+ names.push("");
64560
+ blanked = true;
64561
+ } else {
64562
+ names.push("");
64563
+ }
64564
+ }
64565
+ return names;
64566
+ }
62627
64567
  function evalArgs(argExprs) {
62628
64568
  const args = [];
62629
64569
  for (const a of argExprs) {
@@ -62753,6 +64693,7 @@ function evalFuncCall(expr, nargout) {
62753
64693
  const c = getConstant(expr.name);
62754
64694
  if (c !== void 0) return c;
62755
64695
  }
64696
+ this.pendingInputNames = expr.args.length > 0 ? computeInputNames(expr.args, this.env) : void 0;
62756
64697
  return this.callFunction(expr.name, args, nargout);
62757
64698
  }
62758
64699
  function evalMember(expr, nargout) {
@@ -62932,7 +64873,14 @@ function makeFuncHandle(name) {
62932
64873
  );
62933
64874
  };
62934
64875
  fn.jsFnExpectsNargout = true;
62935
- const narg = getIBuiltinNargin(name);
64876
+ let narg = getIBuiltinNargin(name);
64877
+ if (narg === void 0) {
64878
+ try {
64879
+ narg = this.declaredNargin(name);
64880
+ } catch {
64881
+ narg = void 0;
64882
+ }
64883
+ }
62936
64884
  if (narg !== void 0) fn.nargin = narg;
62937
64885
  if (isNested) {
62938
64886
  fn.releaseExtra = () => capturedEnv.clearLocals();
@@ -63221,6 +65169,7 @@ __export(interpreterFunctions_exports, {
63221
65169
  callNestedFunction: () => callNestedFunction,
63222
65170
  callUserFunction: () => callUserFunction,
63223
65171
  collectClassProperties: () => collectClassProperties,
65172
+ declaredNargin: () => declaredNargin,
63224
65173
  evalInLocalScope: () => evalInLocalScope,
63225
65174
  findExternalMethod: () => findExternalMethod,
63226
65175
  findFunctionInClassFile: () => findFunctionInClassFile,
@@ -63523,6 +65472,30 @@ register("isa", (ctx, args) => {
63523
65472
  if (args.length !== 2) return FALL_THROUGH;
63524
65473
  return ctx.rt.isa(args[0], args[1]);
63525
65474
  });
65475
+ register("superclasses", (ctx, args) => {
65476
+ if (args.length !== 1) return FALL_THROUGH;
65477
+ const v = ensureRuntimeValue(args[0]);
65478
+ let className;
65479
+ if (isRuntimeClassInstance(v) || isRuntimeClassInstanceArray(v)) {
65480
+ className = v.className;
65481
+ } else if (isRuntimeChar(v) || isRuntimeString(v)) {
65482
+ className = toString(v);
65483
+ }
65484
+ const names = [];
65485
+ if (className) {
65486
+ const seen = /* @__PURE__ */ new Set([className]);
65487
+ let current = ctx.rt.getClassParentName(className);
65488
+ while (current && !seen.has(current)) {
65489
+ names.push(current);
65490
+ seen.add(current);
65491
+ current = ctx.rt.getClassParentName(current);
65492
+ }
65493
+ }
65494
+ return RTV.cell(
65495
+ names.map((n) => RTV.string(n)),
65496
+ [names.length, 1]
65497
+ );
65498
+ });
63526
65499
  register("__inferred_type_str", (_ctx, args) => {
63527
65500
  if (args.length !== 1) return FALL_THROUGH;
63528
65501
  const rv = ensureRuntimeValue(args[0]);
@@ -63547,6 +65520,44 @@ register("nargout", (ctx, args) => {
63547
65520
  const v = ctx.env.get("$nargout");
63548
65521
  return v !== void 0 ? v : 0;
63549
65522
  });
65523
+ register("inputname", (ctx, args) => {
65524
+ if (args.length !== 1) return FALL_THROUGH;
65525
+ const narginVal = ctx.env.get("$nargin");
65526
+ if (typeof narginVal !== "number") {
65527
+ throw new RuntimeError(
65528
+ "You can only call 'inputname' from within a MATLAB function."
65529
+ );
65530
+ }
65531
+ const k = toNumber(ensureRuntimeValue(args[0]));
65532
+ if (!Number.isInteger(k) || k < 1) {
65533
+ throw new RuntimeError(
65534
+ "Argument number must be a positive integer scalar."
65535
+ );
65536
+ }
65537
+ if (k > narginVal) {
65538
+ throw new RuntimeError(
65539
+ "Argument number exceeds number of function input arguments."
65540
+ );
65541
+ }
65542
+ const names = ctx.env.inputArgNames;
65543
+ const name = names && k <= names.length ? names[k - 1] : "";
65544
+ return RTV.char(name);
65545
+ });
65546
+ register("properties", (ctx, args) => {
65547
+ if (args.length !== 1) return FALL_THROUGH;
65548
+ const v = ensureRuntimeValue(args[0]);
65549
+ let names;
65550
+ if (isRuntimeClassInstance(v) || isRuntimeClassInstanceArray(v)) {
65551
+ names = ctx.classPublicProperties(v.className);
65552
+ } else if (isRuntimeChar(v) || isRuntimeString(v)) {
65553
+ names = ctx.classPublicProperties(toString(v));
65554
+ }
65555
+ if (!names) return RTV.cell([], [0, 1]);
65556
+ return RTV.cell(
65557
+ names.map((n) => RTV.string(n)),
65558
+ [names.length, 1]
65559
+ );
65560
+ });
63550
65561
  register("narginchk", (ctx, args) => {
63551
65562
  if (args.length !== 2) return FALL_THROUGH;
63552
65563
  const narginVal = ctx.env.get("$nargin");
@@ -63651,10 +65662,14 @@ function callFunction(name, args, nargout) {
63651
65662
  source: ""
63652
65663
  };
63653
65664
  return void 0;
63654
- }
65665
+ },
65666
+ classPublicProperties: (n) => classPublicProperties(this, n)
63655
65667
  };
63656
65668
  const result = specialHandler(ctx, args, nargout);
63657
- if (result !== FALL_THROUGH) return result;
65669
+ if (result !== FALL_THROUGH) {
65670
+ this.pendingInputNames = void 0;
65671
+ return result;
65672
+ }
63658
65673
  }
63659
65674
  const nested = this.env.getNestedFunction(name);
63660
65675
  if (nested) {
@@ -63674,6 +65689,75 @@ function callFunction(name, args, nargout) {
63674
65689
  }
63675
65690
  throw new RuntimeError(`Undefined function or variable '${name}'`);
63676
65691
  }
65692
+ function narginFromParams(params) {
65693
+ const hasVarargin = params.length > 0 && params[params.length - 1] === "varargin";
65694
+ return hasVarargin ? -params.length : params.length;
65695
+ }
65696
+ function paramsInFile(interp, fileName, funcName) {
65697
+ const ast = interp.ctx.getCachedAST(fileName);
65698
+ for (const stmt of ast.body) {
65699
+ if (stmt.type === "Function" && stmt.name === funcName) return stmt.params;
65700
+ }
65701
+ return void 0;
65702
+ }
65703
+ function declaredNargin(name) {
65704
+ const nested = this.env.getNestedFunction(name);
65705
+ if (nested) return narginFromParams(nested.fn.params);
65706
+ const callSite = {
65707
+ file: this.currentFile,
65708
+ ...this.currentClassName ? { className: this.currentClassName } : {},
65709
+ ...this.currentMethodName ? { methodName: this.currentMethodName } : {}
65710
+ };
65711
+ const target = resolveFunction(name, [], callSite, this.functionIndex);
65712
+ if (!target) return void 0;
65713
+ let params;
65714
+ switch (target.kind) {
65715
+ case "localFunction": {
65716
+ const { source } = target;
65717
+ if (source.from === "main") {
65718
+ params = this.mainLocalFunctions.get(target.name)?.params;
65719
+ } else if (source.from === "workspaceFile") {
65720
+ params = this.findFunctionInWorkspaceFile(source.wsName, target.name)?.params ?? void 0;
65721
+ } else if (source.from === "classFile") {
65722
+ params = this.findFunctionInClassFile(
65723
+ source.className,
65724
+ target.name,
65725
+ source.methodScope
65726
+ )?.params ?? void 0;
65727
+ } else if (source.from === "privateFile") {
65728
+ params = paramsInFile(this, source.callerFile, target.name);
65729
+ }
65730
+ break;
65731
+ }
65732
+ case "workspaceFunction": {
65733
+ const dotIdx = target.name.lastIndexOf(".");
65734
+ const primaryName = dotIdx >= 0 ? target.name.slice(dotIdx + 1) : target.name;
65735
+ params = this.findFunctionInWorkspaceFile(target.name, primaryName)?.params ?? void 0;
65736
+ if (params === void 0) {
65737
+ const entry = this.ctx.registry.filesByFuncName.get(target.name);
65738
+ if (entry) {
65739
+ const ast = this.ctx.getCachedAST(entry.fileName);
65740
+ for (const stmt of ast.body) {
65741
+ if (stmt.type === "Function") {
65742
+ params = stmt.params;
65743
+ break;
65744
+ }
65745
+ }
65746
+ }
65747
+ }
65748
+ break;
65749
+ }
65750
+ case "privateFunction": {
65751
+ const entry = this.ctx.getPrivateFileEntry(
65752
+ target.callerFile,
65753
+ target.name
65754
+ );
65755
+ if (entry) params = paramsInFile(this, entry.fileName, target.name);
65756
+ break;
65757
+ }
65758
+ }
65759
+ return params !== void 0 ? narginFromParams(params) : void 0;
65760
+ }
63677
65761
  function interpretTarget(target, args, nargout) {
63678
65762
  switch (target.kind) {
63679
65763
  case "builtin": {
@@ -63842,18 +65926,12 @@ function interpretWorkspaceFunction(target, args, nargout) {
63842
65926
  if (entry) {
63843
65927
  const ast = this.ctx.getCachedAST(entry.fileName);
63844
65928
  return this.withFileContext(entry.fileName, void 0, void 0, () => {
63845
- const savedEnv = this.env;
63846
- this.env = new Environment();
63847
- try {
63848
- for (const stmt of ast.body) {
63849
- if (stmt.type === "Function") continue;
63850
- const signal = this.execStmt(stmt);
63851
- if (signal instanceof ReturnSignal) break;
63852
- }
63853
- return this.ans;
63854
- } finally {
63855
- this.env = savedEnv;
65929
+ for (const stmt of ast.body) {
65930
+ if (stmt.type === "Function") continue;
65931
+ const signal = this.execStmt(stmt);
65932
+ if (signal instanceof ReturnSignal) break;
63856
65933
  }
65934
+ return this.ans;
63857
65935
  });
63858
65936
  }
63859
65937
  throw new RuntimeError(`Workspace function '${target.name}' not found`);
@@ -63920,6 +65998,21 @@ function instantiateClass(className, args, nargout) {
63920
65998
  if (!classInfo) {
63921
65999
  return this.rt.callClassMethod(className, className, nargout, args);
63922
66000
  }
66001
+ if (classInfo.isOldStyle) {
66002
+ const ctorName = classInfo.constructorName ?? className;
66003
+ const ctorFn = this.findExternalMethod(classInfo, ctorName);
66004
+ if (!ctorFn)
66005
+ throw new RuntimeError(
66006
+ `Constructor for old-style class '${className}' not found`
66007
+ );
66008
+ const fileName = classInfo.externalMethodFiles.get(ctorName)?.fileName ?? classInfo.fileName;
66009
+ return this.withFileContext(
66010
+ fileName,
66011
+ className,
66012
+ ctorName,
66013
+ () => this.callUserFunction(ctorFn, args, nargout)
66014
+ );
66015
+ }
63923
66016
  const { propertyNames, propertyDefaults } = this.collectClassProperties(classInfo);
63924
66017
  const defaults = /* @__PURE__ */ new Map();
63925
66018
  for (const [propName, defaultExpr] of propertyDefaults) {
@@ -63943,7 +66036,7 @@ function instantiateClass(className, args, nargout) {
63943
66036
  function interpretConstructor(classInfo, args, nargout) {
63944
66037
  const constructorName = classInfo.constructorName;
63945
66038
  if (!constructorName) return args[0];
63946
- for (const member of classInfo.ast.members) {
66039
+ for (const member of classInfo.ast?.members ?? []) {
63947
66040
  if (member.type !== "Methods") continue;
63948
66041
  for (const methodStmt of member.body) {
63949
66042
  if (methodStmt.type === "Function" && methodStmt.name === constructorName) {
@@ -63985,6 +66078,8 @@ function callUserFunction(fn, args, nargout, narginOverride) {
63985
66078
  if (!hasVarargoutDecl && nargout > declaredRegularOutputs) {
63986
66079
  throw new RuntimeError("Too many output arguments.");
63987
66080
  }
66081
+ const callInputNames = this.pendingInputNames;
66082
+ this.pendingInputNames = void 0;
63988
66083
  const sharedArgs = args;
63989
66084
  if (narginOverride === void 0 && this.registry.size > 0) {
63990
66085
  const r = this.registry.dispatchCall(fn, sharedArgs, nargout, this);
@@ -64018,6 +66113,7 @@ function callUserFunction(fn, args, nargout, narginOverride) {
64018
66113
  }
64019
66114
  fnEnv.set("$nargin", narginOverride ?? args.length);
64020
66115
  fnEnv.set("$nargout", nargout);
66116
+ fnEnv.inputArgNames = callInputNames;
64021
66117
  for (const stmt of fn.body) {
64022
66118
  if (stmt.type === "Function") {
64023
66119
  fnEnv.nestedFunctions.set(stmt.name, {
@@ -64094,10 +66190,13 @@ function callUserFunction(fn, args, nargout, narginOverride) {
64094
66190
  }
64095
66191
  }
64096
66192
  function callNestedFunction(fn, parentEnv, args, nargout) {
66193
+ const callInputNames = this.pendingInputNames;
66194
+ this.pendingInputNames = void 0;
64097
66195
  const fnEnv = new Environment(parentEnv);
64098
66196
  fnEnv.isNested = true;
64099
66197
  fnEnv.rt = this.rt;
64100
66198
  fnEnv.persistentFuncId = `${this.currentFile}:${fn.name}`;
66199
+ fnEnv.nestedLocalNames = /* @__PURE__ */ new Set([...fn.params, ...fn.outputs]);
64101
66200
  const hasVarargin = fn.params.length > 0 && fn.params[fn.params.length - 1] === "varargin";
64102
66201
  const regularParams = hasVarargin ? fn.params.slice(0, -1) : fn.params;
64103
66202
  for (let i = 0; i < regularParams.length; i++) {
@@ -64111,6 +66210,15 @@ function callNestedFunction(fn, parentEnv, args, nargout) {
64111
66210
  }
64112
66211
  fnEnv.setLocal("$nargin", args.length);
64113
66212
  fnEnv.setLocal("$nargout", nargout);
66213
+ fnEnv.inputArgNames = callInputNames;
66214
+ for (const stmt of fn.body) {
66215
+ if (stmt.type === "Function") {
66216
+ fnEnv.nestedFunctions.set(stmt.name, {
66217
+ fn: funcDefFromStmt(stmt),
66218
+ env: fnEnv
66219
+ });
66220
+ }
66221
+ }
64114
66222
  const savedEnv = this.env;
64115
66223
  this.env = fnEnv;
64116
66224
  this.rt.pushCleanupScope();
@@ -64249,7 +66357,7 @@ function findMethodInClass(classInfo, methodName) {
64249
66357
  const cacheKey = `method:${classInfo.name}:${methodName}`;
64250
66358
  const cached = this.functionDefCache.get(cacheKey);
64251
66359
  if (cached) return cached;
64252
- for (const member of classInfo.ast.members) {
66360
+ for (const member of classInfo.ast?.members ?? []) {
64253
66361
  if (member.type !== "Methods") continue;
64254
66362
  for (const methodStmt of member.body) {
64255
66363
  if (methodStmt.type === "Function" && methodStmt.name === methodName) {
@@ -64306,6 +66414,36 @@ function collectClassProperties(classInfo) {
64306
66414
  }
64307
66415
  return { propertyNames, propertyDefaults };
64308
66416
  }
66417
+ function classPublicProperties(interp, className) {
66418
+ let info = interp.ctx.getClassInfo(className);
66419
+ if (!info) return void 0;
66420
+ const out = [];
66421
+ const seen = /* @__PURE__ */ new Set();
66422
+ while (info) {
66423
+ const ast = info.ast;
66424
+ if (ast) {
66425
+ for (const member of ast.members) {
66426
+ if (member.type !== "Properties") continue;
66427
+ const hidden = member.attributes.some((a) => {
66428
+ const n = a.name.toLowerCase();
66429
+ if (n !== "access" && n !== "getaccess") return false;
66430
+ const val = (a.value ?? "").toLowerCase();
66431
+ return val === "private" || val === "protected";
66432
+ });
66433
+ if (hidden) continue;
66434
+ for (const pn of member.names) {
66435
+ if (!seen.has(pn)) {
66436
+ seen.add(pn);
66437
+ out.push(pn);
66438
+ }
66439
+ }
66440
+ }
66441
+ }
66442
+ if (!info.superClass || info.superClass === "handle") break;
66443
+ info = interp.ctx.getClassInfo(info.superClass);
66444
+ }
66445
+ return out;
66446
+ }
64309
66447
  function isHandleClass(classInfo) {
64310
66448
  let parentName = classInfo.superClass;
64311
66449
  while (parentName) {
@@ -64424,6 +66562,10 @@ var Interpreter = class {
64424
66562
  workspaceEnv;
64425
66563
  /** @internal The caller's environment — for evalin/assignin('caller', ...) */
64426
66564
  callerEnv;
66565
+ /** @internal Call-site variable names for the next user-function call, set
66566
+ * by `evalFuncCall` and consumed by `callUserFunction` to support
66567
+ * `inputname`. One-shot: cleared as soon as it is consumed. */
66568
+ pendingInputNames;
64427
66569
  /** @internal Stack of [base, dimIndex, numIndices] for resolving `end` keyword in indexing. */
64428
66570
  endContextStack = [];
64429
66571
  /** @internal Number of enclosing `for` / `while` loop bodies the
@@ -64685,6 +66827,37 @@ function extractClassInfo(classDef, qualifiedName, fileName, source) {
64685
66827
  externalMethodFiles: /* @__PURE__ */ new Map()
64686
66828
  };
64687
66829
  }
66830
+ function makeOldStyleClassInfo(qualifiedName, baseName, constructorFile, methodFiles) {
66831
+ const externalMethodFiles = /* @__PURE__ */ new Map();
66832
+ externalMethodFiles.set(baseName, {
66833
+ fileName: constructorFile.fileName,
66834
+ source: constructorFile.source
66835
+ });
66836
+ const methodNames = /* @__PURE__ */ new Set();
66837
+ for (const mf of methodFiles) {
66838
+ externalMethodFiles.set(mf.name, {
66839
+ fileName: mf.fileName,
66840
+ source: mf.source
66841
+ });
66842
+ methodNames.add(mf.name);
66843
+ }
66844
+ return {
66845
+ name: baseName,
66846
+ qualifiedName,
66847
+ fileName: constructorFile.fileName,
66848
+ source: constructorFile.source,
66849
+ superClass: null,
66850
+ propertyNames: [],
66851
+ propertyDefaults: /* @__PURE__ */ new Map(),
66852
+ methodNames,
66853
+ staticMethodNames: /* @__PURE__ */ new Set(),
66854
+ constructorName: baseName,
66855
+ inferiorClasses: [],
66856
+ ast: null,
66857
+ isOldStyle: true,
66858
+ externalMethodFiles
66859
+ };
66860
+ }
64688
66861
 
64689
66862
  // src/numbl-core/lowering/loweringContext.ts
64690
66863
  function createWorkspaceRegistry() {
@@ -64877,8 +67050,41 @@ var LoweringContext = class _LoweringContext {
64877
67050
  }
64878
67051
  }
64879
67052
  }
67053
+ const startsWithClassdef = (source) => {
67054
+ let t = source.trimStart();
67055
+ while (t.startsWith("%")) {
67056
+ const nl = t.indexOf("\n");
67057
+ if (nl < 0) return false;
67058
+ t = t.slice(nl + 1).trimStart();
67059
+ }
67060
+ return t.startsWith("classdef");
67061
+ };
64880
67062
  for (const [className, group] of classFolderGroups) {
64881
- if (!group.classDefFile) {
67063
+ const hasRealClassdef = !!group.classDefFile && startsWithClassdef(group.classDefFile.source);
67064
+ if (!hasRealClassdef) {
67065
+ const dotIdx = className.lastIndexOf(".");
67066
+ const baseName = dotIdx >= 0 ? className.slice(dotIdx + 1) : className;
67067
+ const methodBase = (f) => f.name.split("/").pop().replace(/\.m$/, "");
67068
+ const ctorFile = group.classDefFile ?? group.methodFiles.find((f) => methodBase(f) === baseName);
67069
+ if (!ctorFile) {
67070
+ continue;
67071
+ }
67072
+ const methodFiles = group.methodFiles.filter((f) => f !== ctorFile).map((f) => ({
67073
+ name: methodBase(f),
67074
+ fileName: f.name,
67075
+ source: f.source
67076
+ }));
67077
+ if (!this.registry.classesByName.has(className)) {
67078
+ this.registry.classesByName.set(
67079
+ className,
67080
+ makeOldStyleClassInfo(
67081
+ className,
67082
+ baseName,
67083
+ { fileName: ctorFile.name, source: ctorFile.source },
67084
+ methodFiles
67085
+ )
67086
+ );
67087
+ }
64882
67088
  continue;
64883
67089
  }
64884
67090
  this.registerWorkspaceClass(className, group.classDefFile);
@@ -65114,7 +67320,7 @@ var LoweringContext = class _LoweringContext {
65114
67320
  if (!info) return null;
65115
67321
  if (info.ctx) return info.ctx;
65116
67322
  const ctx = new _LoweringContext(info.source, info.fileName);
65117
- for (const member of info.ast.members) {
67323
+ for (const member of info.ast?.members ?? []) {
65118
67324
  if (member.type !== "Methods") continue;
65119
67325
  for (const methodStmt of member.body) {
65120
67326
  if (methodStmt.type !== "Function") continue;
@@ -65273,6 +67479,7 @@ var LoweringContext = class _LoweringContext {
65273
67479
  for (const m of info.methodNames) instanceMethods.add(m);
65274
67480
  for (const m of info.staticMethodNames) staticMethods.add(m);
65275
67481
  for (const m of info.externalMethodFiles.keys()) {
67482
+ if (info.isOldStyle && m === info.constructorName) continue;
65276
67483
  if (!info.staticMethodNames.has(m)) {
65277
67484
  instanceMethods.add(m);
65278
67485
  }
@@ -65316,10 +67523,43 @@ var LoweringContext = class _LoweringContext {
65316
67523
  }
65317
67524
  }
65318
67525
  const fileImports = /* @__PURE__ */ new Map();
67526
+ const gatherImports = (body, out) => {
67527
+ for (const stmt of body) {
67528
+ switch (stmt.type) {
67529
+ case "Import":
67530
+ out.push(stmt);
67531
+ break;
67532
+ case "Function":
67533
+ case "While":
67534
+ case "For":
67535
+ gatherImports(stmt.body, out);
67536
+ break;
67537
+ case "If":
67538
+ gatherImports(stmt.thenBody, out);
67539
+ for (const b of stmt.elseifBlocks) gatherImports(b.body, out);
67540
+ if (stmt.elseBody) gatherImports(stmt.elseBody, out);
67541
+ break;
67542
+ case "Switch":
67543
+ for (const c of stmt.cases) gatherImports(c.body, out);
67544
+ if (stmt.otherwise) gatherImports(stmt.otherwise, out);
67545
+ break;
67546
+ case "TryCatch":
67547
+ gatherImports(stmt.tryBody, out);
67548
+ gatherImports(stmt.catchBody, out);
67549
+ break;
67550
+ case "ClassDef":
67551
+ for (const member of stmt.members) {
67552
+ if (member.type === "Methods") gatherImports(member.body, out);
67553
+ }
67554
+ break;
67555
+ }
67556
+ }
67557
+ };
65319
67558
  const collectImportsFromBody = (body, fileName) => {
65320
67559
  const entries = [];
65321
- for (const stmt of body) {
65322
- if (stmt.type !== "Import") continue;
67560
+ const importStmts = [];
67561
+ gatherImports(body, importStmts);
67562
+ for (const stmt of importStmts) {
65323
67563
  if (stmt.wildcard) {
65324
67564
  entries.push({ wildcard: true, namespace: stmt.path.join(".") });
65325
67565
  } else {
@@ -65394,6 +67634,10 @@ var LoweringContext = class _LoweringContext {
65394
67634
 
65395
67635
  // src/numbl-core/stdlib-bundle.ts
65396
67636
  var stdlibFiles = [
67637
+ {
67638
+ name: "TriRep.m",
67639
+ source: "classdef TriRep < triangulation\n % (Not recommended) Triangulation representation. Provided for legacy\n % code; use triangulation instead. TriRep inherits freeBoundary,\n % vertexAttachments, etc. from triangulation and exposes the legacy\n % property names X (vertex coordinates) and Triangulation (connectivity).\n %\n % TR = TriRep(tri, x, y)\n % TR = TriRep(tri, x, y, z)\n % TR = TriRep(tri, P)\n properties\n X\n Triangulation\n end\n methods\n function obj = TriRep(tri, varargin)\n obj = obj@triangulation(tri, varargin{:});\n obj.X = obj.Points;\n obj.Triangulation = obj.ConnectivityList;\n end\n end\nend\n"
67640
+ },
65397
67641
  {
65398
67642
  name: "addOptional.m",
65399
67643
  source: "function addOptional(obj, name, default, validator)\n if nargin < 4\n obj.addOptional(name, default);\n else\n obj.addOptional(name, default, validator);\n end\nend\n"
@@ -65421,6 +67665,10 @@ var stdlibFiles = [
65421
67665
  {
65422
67666
  name: "readmatrix.m",
65423
67667
  source: "function A = readmatrix(filename, varargin)\n % Parse name-value pairs\n delimiter = '';\n numHeaderLines = -1; % -1 means auto-detect\n\n i = 1;\n while i <= length(varargin)\n if ischar(varargin{i}) || isstring(varargin{i})\n key = lower(char(varargin{i}));\n if strcmp(key, 'delimiter')\n delimiter = char(varargin{i+1});\n i = i + 2;\n elseif strcmp(key, 'numheaderlines')\n numHeaderLines = varargin{i+1};\n i = i + 2;\n else\n % Skip unknown name-value pairs\n i = i + 2;\n end\n else\n i = i + 1;\n end\n end\n\n % Auto-detect delimiter from extension if not specified\n if isempty(delimiter)\n [~, ~, ext] = fileparts(filename);\n if strcmp(ext, '.csv')\n delimiter = ',';\n else\n delimiter = ''; % will split on whitespace\n end\n end\n\n % Read the entire file\n txt = fileread(filename);\n\n % Split into lines\n lines = strsplit(txt, sprintf('\\n'));\n\n % Remove trailing empty line (from trailing newline)\n if ~isempty(lines) && strcmp(strtrim(char(lines{end})), '')\n lines = lines(1:end-1);\n end\n\n if isempty(lines)\n A = [];\n return;\n end\n\n % Auto-detect header lines: skip lines that can't be fully parsed as numbers\n if numHeaderLines < 0\n numHeaderLines = 0;\n for k = 1:length(lines)\n line = strtrim(char(lines{k}));\n if strcmp(line, '')\n numHeaderLines = numHeaderLines + 1;\n continue;\n end\n if ~isempty(delimiter)\n parts = strsplit(line, delimiter);\n else\n parts = strsplit(line);\n end\n allNumeric = true;\n for j = 1:length(parts)\n val = str2double(strtrim(char(parts{j})));\n if isnan(val)\n token = strtrim(char(parts{j}));\n % Allow NaN, Inf, -Inf as valid numeric tokens\n if ~strcmpi(token, 'nan') && ~strcmpi(token, 'inf') && ~strcmpi(token, '-inf')\n allNumeric = false;\n break;\n end\n end\n end\n if allNumeric\n break;\n else\n numHeaderLines = numHeaderLines + 1;\n end\n end\n end\n\n % Parse data lines\n dataLines = lines(numHeaderLines+1:end);\n nRows = length(dataLines);\n if nRows == 0\n A = [];\n return;\n end\n\n % First pass: determine number of columns from first data line\n firstLine = strtrim(char(dataLines{1}));\n if ~isempty(delimiter)\n parts = strsplit(firstLine, delimiter);\n else\n parts = strsplit(firstLine);\n end\n nCols = length(parts);\n\n A = zeros(nRows, nCols);\n for r = 1:nRows\n line = strtrim(char(dataLines{r}));\n if strcmp(line, '')\n A(r, :) = NaN;\n continue;\n end\n if ~isempty(delimiter)\n parts = strsplit(line, delimiter);\n else\n parts = strsplit(line);\n end\n for c = 1:min(length(parts), nCols)\n val = str2double(strtrim(char(parts{c})));\n if isnan(val)\n token = strtrim(char(parts{c}));\n if strcmpi(token, 'nan')\n A(r, c) = NaN;\n elseif strcmpi(token, 'inf')\n A(r, c) = Inf;\n elseif strcmpi(token, '-inf')\n A(r, c) = -Inf;\n else\n A(r, c) = NaN; % non-numeric data becomes NaN\n end\n else\n A(r, c) = val;\n end\n end\n % Fill missing columns with NaN\n if length(parts) < nCols\n A(r, length(parts)+1:nCols) = NaN;\n end\n end\nend\n"
67668
+ },
67669
+ {
67670
+ name: "triangulation.m",
67671
+ source: "classdef triangulation\n % Triangulation representation providing topological queries over a\n % triangle (or tetrahedron) mesh. Mirrors a subset of MATLAB's\n % triangulation class.\n %\n % TR = triangulation(tri, P) % P is an n-by-d coordinate matrix\n % TR = triangulation(tri, x, y) % 2-D vertex coordinate columns\n % TR = triangulation(tri, x, y, z) % 3-D vertex coordinate columns\n properties\n Points\n ConnectivityList\n end\n methods\n function obj = triangulation(tri, varargin)\n if nargin == 0\n return;\n end\n obj.ConnectivityList = tri;\n if numel(varargin) == 1\n obj.Points = varargin{1};\n elseif numel(varargin) >= 2\n obj.Points = [varargin{:}];\n else\n error('triangulation: vertex coordinates are required');\n end\n end\n\n function [F, P] = freeBoundary(obj)\n % Free boundary facets: the edges referenced by exactly one\n % triangle, ordered into connected, consistently oriented loops.\n tri = obj.ConnectivityList;\n E = [tri(:, [1 2]); tri(:, [2 3]); tri(:, [3 1])];\n Es = sort(E, 2);\n [~, ~, ic] = unique(Es, 'rows');\n counts = accumarray(ic, 1);\n isB = counts(ic) == 1;\n bedges = E(isB, :);\n\n n = size(bedges, 1);\n F = zeros(n, 2);\n used = false(n, 1);\n pos = 1;\n while pos <= n\n startRow = find(~used, 1);\n if isempty(startRow)\n break;\n end\n cur = startRow;\n loopStart = bedges(cur, 1);\n while true\n F(pos, :) = bedges(cur, :);\n used(cur) = true;\n pos = pos + 1;\n nextv = bedges(cur, 2);\n if nextv == loopStart\n break;\n end\n cand = find(~used & bedges(:, 1) == nextv, 1);\n if isempty(cand)\n break;\n end\n cur = cand;\n end\n end\n\n if nargout > 1\n vid = unique(F(:));\n P = obj.Points(vid, :);\n remap = zeros(max(vid), 1);\n remap(vid) = 1:numel(vid);\n F = remap(F);\n end\n end\n\n function V = vertexAttachments(obj, id)\n % IDs of the triangles attached to each vertex, returned as a\n % cell array with one row vector of triangle IDs per vertex.\n tri = obj.ConnectivityList;\n nv = size(obj.Points, 1);\n if nargin < 2\n id = (1:nv)';\n end\n id = id(:);\n V = cell(numel(id), 1);\n for k = 1:numel(id)\n V{k} = find(any(tri == id(k), 2))';\n end\n end\n end\nend\n"
65424
67672
  }
65425
67673
  ];
65426
67674
  var shimFiles = [
@@ -74963,6 +77211,30 @@ var sin = defineUnaryRealMath({
74963
77211
  complex: { cFnComplex: "mtoc2_csin", jsFnComplex: cSin }
74964
77212
  });
74965
77213
 
77214
+ // src/numbl-core/jit/builtins/defs/math/rand.ts
77215
+ var rand = {
77216
+ name: "rand",
77217
+ transfer(argTypes, nargout) {
77218
+ if (nargout > 1) {
77219
+ throw new UnsupportedConstruct(
77220
+ `'rand' does not support multi-output (nargout=${nargout})`
77221
+ );
77222
+ }
77223
+ if (argTypes.length !== 0) {
77224
+ throw new UnsupportedConstruct(
77225
+ `JS-JIT 'rand' supports only the scalar form rand() so far (got ${argTypes.length} arg(s)); matrix/seed forms run in the interpreter`
77226
+ );
77227
+ }
77228
+ return [scalarDouble("nonneg")];
77229
+ },
77230
+ emitJs() {
77231
+ return "$rand()";
77232
+ },
77233
+ call() {
77234
+ return [rngRandom()];
77235
+ }
77236
+ };
77237
+
74966
77238
  // src/numbl-core/jit/builtins/defs/math/tan.ts
74967
77239
  var tan = defineUnaryRealMath({
74968
77240
  name: "tan",
@@ -80346,6 +82618,7 @@ for (const b of [
80346
82618
  stdBuiltin,
80347
82619
  min,
80348
82620
  max,
82621
+ rand,
80349
82622
  any,
80350
82623
  all,
80351
82624
  zeros,
@@ -86131,12 +88404,13 @@ function emitJsProgram(prog, opts = {}) {
86131
88404
  const wrapperLines = [];
86132
88405
  if (opts.exposeSpec !== void 0) {
86133
88406
  wrapperLines.push(
86134
- `return function ($h) { globalThis.$write = $h.write; globalThis.$plotDispatch = $h.plotDispatch; return ${opts.exposeSpec}; };`
88407
+ `return function ($h) { globalThis.$write = $h.write; globalThis.$plotDispatch = $h.plotDispatch; globalThis.$rand = $h.rand; return ${opts.exposeSpec}; };`
86135
88408
  );
86136
88409
  } else {
86137
88410
  wrapperLines.push("function run($h) {");
86138
88411
  wrapperLines.push(" globalThis.$write = $h.write;");
86139
88412
  wrapperLines.push(" globalThis.$plotDispatch = $h.plotDispatch;");
88413
+ wrapperLines.push(" globalThis.$rand = $h.rand;");
86140
88414
  const locals = collectAssignedLocals(prog.topLevelStmts);
86141
88415
  if (locals.length > 0) {
86142
88416
  wrapperLines.push(` let ${locals.join(", ")};`);
@@ -87684,6 +89958,7 @@ var Workspace = class _Workspace {
87684
89958
  }
87685
89959
  };
87686
89960
  for (const [name, info] of this.ctx.registry.classesByName) {
89961
+ if (info.isOldStyle) continue;
87687
89962
  registerOrDefer(
87688
89963
  name,
87689
89964
  () => registerClassDef(
@@ -87697,7 +89972,7 @@ var Workspace = class _Workspace {
87697
89972
  if (this.classes.has(name) || this.failedClassValidations.has(name)) {
87698
89973
  throw new UnsupportedConstruct(
87699
89974
  `class '${name}' is defined both locally and as a workspace class`,
87700
- info.ast.span
89975
+ info.ast?.span
87701
89976
  );
87702
89977
  }
87703
89978
  registerOrDefer(name, () => registerClassDef(info.ast, info.fileName));
@@ -87791,7 +90066,7 @@ var Workspace = class _Workspace {
87791
90066
  if (!ast) {
87792
90067
  throw new UnsupportedConstruct(
87793
90068
  `internal: external method file '${mf.fileName}' for '${info.qualifiedName}.${methodName}' was not parsed`,
87794
- info.ast.span
90069
+ info.ast?.span
87795
90070
  );
87796
90071
  }
87797
90072
  let primary = null;
@@ -87807,7 +90082,7 @@ var Workspace = class _Workspace {
87807
90082
  if (!primary) {
87808
90083
  throw new UnsupportedConstruct(
87809
90084
  `external method file '${mf.fileName}' has no function`,
87810
- info.ast.span
90085
+ info.ast?.span
87811
90086
  );
87812
90087
  }
87813
90088
  out.set(methodName, primary);
@@ -88172,6 +90447,7 @@ function getOrCreateSession(interp) {
88172
90447
  function buildHostHelpers(rt) {
88173
90448
  return {
88174
90449
  write: (s) => rt.output(s),
90450
+ rand: () => rngRandom(),
88175
90451
  plotDispatch: (name, args) => {
88176
90452
  const runtimeArgs = args.map((a) => jitToNumbl(a));
88177
90453
  const handled = dispatchPlotBuiltin(
@@ -88264,6 +90540,11 @@ var jitCallExecutor = {
88264
90540
  return { specFn };
88265
90541
  } catch (e) {
88266
90542
  if (e instanceof UnsupportedConstruct || e instanceof TypeError2) {
90543
+ recordJitDecline({
90544
+ message: e.message,
90545
+ kind: e.constructor.name,
90546
+ where: "jit-call"
90547
+ });
88267
90548
  return null;
88268
90549
  }
88269
90550
  throw e;
@@ -88390,6 +90671,11 @@ var jitLoopExecutor = {
88390
90671
  return { specFn };
88391
90672
  } catch (e) {
88392
90673
  if (e instanceof UnsupportedConstruct || e instanceof TypeError2) {
90674
+ recordJitDecline({
90675
+ message: e.message,
90676
+ kind: e.constructor.name,
90677
+ where: "jit-loop"
90678
+ });
88393
90679
  return null;
88394
90680
  }
88395
90681
  throw e;
@@ -88530,6 +90816,11 @@ var jitTopLevelExecutor = {
88530
90816
  return { specFn, nargout };
88531
90817
  } catch (e) {
88532
90818
  if (e instanceof UnsupportedConstruct || e instanceof TypeError2) {
90819
+ recordJitDecline({
90820
+ message: e.message,
90821
+ kind: e.constructor.name,
90822
+ where: "jit-top-level"
90823
+ });
88533
90824
  return null;
88534
90825
  }
88535
90826
  throw e;
@@ -89191,10 +91482,783 @@ async function load() {
89191
91482
  const { loadQhull } = await import("qhull-wasm");
89192
91483
  const qhull = await loadQhull();
89193
91484
  setDelaunayBackend((points, dim) => qhull.delaunay(points, dim).facets);
91485
+ setConvexHullBackend((points, dim) => qhull.convexHull(points, dim).facets);
89194
91486
  }
91487
+
91488
+ // src/vfs/VirtualFileSystem.ts
91489
+ var VirtualFileSystem = class {
91490
+ files = /* @__PURE__ */ new Map();
91491
+ directories = /* @__PURE__ */ new Set();
91492
+ cwd = "/project";
91493
+ // Change tracking
91494
+ createdFiles = /* @__PURE__ */ new Set();
91495
+ modifiedFiles = /* @__PURE__ */ new Set();
91496
+ deletedFiles = /* @__PURE__ */ new Set();
91497
+ /** Clear change tracking. Call after populating the VFS with initial files. */
91498
+ clearChangeTracking() {
91499
+ this.createdFiles.clear();
91500
+ this.modifiedFiles.clear();
91501
+ this.deletedFiles.clear();
91502
+ }
91503
+ /** Normalize a path to absolute form. */
91504
+ normalizePath(p2) {
91505
+ if (!p2.startsWith("/")) {
91506
+ p2 = this.cwd + "/" + p2;
91507
+ }
91508
+ const parts = p2.split("/");
91509
+ const resolved = [];
91510
+ for (const part of parts) {
91511
+ if (part === "" || part === ".") continue;
91512
+ if (part === "..") {
91513
+ if (resolved.length > 0) resolved.pop();
91514
+ } else {
91515
+ resolved.push(part);
91516
+ }
91517
+ }
91518
+ return "/" + resolved.join("/");
91519
+ }
91520
+ getCwd() {
91521
+ return this.cwd;
91522
+ }
91523
+ setCwd(dir) {
91524
+ this.cwd = this.normalizePath(dir);
91525
+ }
91526
+ readFile(path) {
91527
+ const norm2 = this.normalizePath(path);
91528
+ const file = this.files.get(norm2);
91529
+ if (!file) throw new Error(`File not found: ${path}`);
91530
+ return file.content;
91531
+ }
91532
+ writeFile(path, content) {
91533
+ const norm2 = this.normalizePath(path);
91534
+ const existed = this.files.has(norm2);
91535
+ this.files.set(norm2, { content, mtimeMs: Date.now() });
91536
+ this.ensureParentDirs(norm2);
91537
+ if (this.deletedFiles.has(norm2)) {
91538
+ this.deletedFiles.delete(norm2);
91539
+ this.modifiedFiles.add(norm2);
91540
+ } else if (!existed) {
91541
+ this.createdFiles.add(norm2);
91542
+ } else if (!this.createdFiles.has(norm2)) {
91543
+ this.modifiedFiles.add(norm2);
91544
+ }
91545
+ }
91546
+ deleteFile(path) {
91547
+ const norm2 = this.normalizePath(path);
91548
+ if (!this.files.has(norm2)) return false;
91549
+ this.files.delete(norm2);
91550
+ if (this.createdFiles.has(norm2)) {
91551
+ this.createdFiles.delete(norm2);
91552
+ this.modifiedFiles.delete(norm2);
91553
+ } else {
91554
+ this.modifiedFiles.delete(norm2);
91555
+ this.deletedFiles.add(norm2);
91556
+ }
91557
+ return true;
91558
+ }
91559
+ exists(path) {
91560
+ const norm2 = this.normalizePath(path);
91561
+ if (this.files.has(norm2)) return "file";
91562
+ if (this.directories.has(norm2)) return "dir";
91563
+ const prefix = norm2 + "/";
91564
+ for (const key of this.files.keys()) {
91565
+ if (key.startsWith(prefix)) return "dir";
91566
+ }
91567
+ return null;
91568
+ }
91569
+ fileSize(path) {
91570
+ const norm2 = this.normalizePath(path);
91571
+ const file = this.files.get(norm2);
91572
+ return file ? file.content.length : 0;
91573
+ }
91574
+ mkdir(dirPath) {
91575
+ const norm2 = this.normalizePath(dirPath);
91576
+ this.directories.add(norm2);
91577
+ this.ensureParentDirs(norm2 + "/placeholder");
91578
+ return true;
91579
+ }
91580
+ /** Move/rename a file or directory tree. Returns true on success. */
91581
+ movefile(source, destination) {
91582
+ const srcNorm = this.normalizePath(source);
91583
+ const srcType = this.exists(srcNorm);
91584
+ if (srcType === null) return false;
91585
+ let dstNorm = this.normalizePath(destination);
91586
+ const dstType = this.exists(dstNorm);
91587
+ if (dstType === "dir") {
91588
+ const lastSlash = srcNorm.lastIndexOf("/");
91589
+ const baseName = lastSlash >= 0 ? srcNorm.slice(lastSlash + 1) : srcNorm;
91590
+ dstNorm = (dstNorm === "/" ? "" : dstNorm) + "/" + baseName;
91591
+ }
91592
+ if (srcType === "dir" && this.exists(dstNorm) === "file") return false;
91593
+ if (srcType === "file") {
91594
+ const content = this.files.get(srcNorm).content;
91595
+ if (this.exists(dstNorm) === "file") this.deleteFile(dstNorm);
91596
+ this.writeFile(dstNorm, content);
91597
+ this.deleteFile(srcNorm);
91598
+ return true;
91599
+ }
91600
+ const srcPrefix = srcNorm + "/";
91601
+ const filesToMove = [];
91602
+ for (const [path, file] of this.files) {
91603
+ if (path === srcNorm || path.startsWith(srcPrefix)) {
91604
+ const rest = path === srcNorm ? "" : path.slice(srcNorm.length);
91605
+ filesToMove.push({
91606
+ from: path,
91607
+ to: dstNorm + rest,
91608
+ content: file.content
91609
+ });
91610
+ }
91611
+ }
91612
+ const dirsToMove = [];
91613
+ for (const dir of this.directories) {
91614
+ if (dir === srcNorm || dir.startsWith(srcPrefix)) {
91615
+ const rest = dir === srcNorm ? "" : dir.slice(srcNorm.length);
91616
+ dirsToMove.push({ from: dir, to: dstNorm + rest });
91617
+ }
91618
+ }
91619
+ for (const { from } of filesToMove) this.deleteFile(from);
91620
+ for (const { from } of dirsToMove) this.directories.delete(from);
91621
+ this.directories.add(dstNorm);
91622
+ for (const { to } of dirsToMove) this.directories.add(to);
91623
+ for (const { to, content } of filesToMove) this.writeFile(to, content);
91624
+ return true;
91625
+ }
91626
+ /** Copy a file or directory tree (source is left in place). True on success. */
91627
+ copyfile(source, destination) {
91628
+ const srcNorm = this.normalizePath(source);
91629
+ const srcType = this.exists(srcNorm);
91630
+ if (srcType === null) return false;
91631
+ let dstNorm = this.normalizePath(destination);
91632
+ if (this.exists(dstNorm) === "dir") {
91633
+ const lastSlash = srcNorm.lastIndexOf("/");
91634
+ const baseName = lastSlash >= 0 ? srcNorm.slice(lastSlash + 1) : srcNorm;
91635
+ dstNorm = (dstNorm === "/" ? "" : dstNorm) + "/" + baseName;
91636
+ }
91637
+ if (srcType === "dir" && this.exists(dstNorm) === "file") return false;
91638
+ if (srcType === "file") {
91639
+ const content = this.files.get(srcNorm).content;
91640
+ if (this.exists(dstNorm) === "file") this.deleteFile(dstNorm);
91641
+ this.writeFile(dstNorm, content);
91642
+ return true;
91643
+ }
91644
+ const srcPrefix = srcNorm + "/";
91645
+ for (const [path, file] of [...this.files]) {
91646
+ if (path === srcNorm || path.startsWith(srcPrefix)) {
91647
+ const rest = path === srcNorm ? "" : path.slice(srcNorm.length);
91648
+ this.writeFile(dstNorm + rest, file.content);
91649
+ }
91650
+ }
91651
+ for (const dir of [...this.directories]) {
91652
+ if (dir === srcNorm || dir.startsWith(srcPrefix)) {
91653
+ const rest = dir === srcNorm ? "" : dir.slice(srcNorm.length);
91654
+ this.directories.add(dstNorm + rest);
91655
+ }
91656
+ }
91657
+ this.directories.add(dstNorm);
91658
+ return true;
91659
+ }
91660
+ /** Resolve a path to its (normalized) absolute path + attributes, or null. */
91661
+ fileattrib(path) {
91662
+ const norm2 = this.normalizePath(path);
91663
+ const t = this.exists(norm2);
91664
+ if (t === null) return null;
91665
+ return {
91666
+ Name: norm2,
91667
+ directory: t === "dir",
91668
+ UserRead: true,
91669
+ UserWrite: true,
91670
+ UserExecute: t === "dir"
91671
+ };
91672
+ }
91673
+ rmdir(dirPath, recursive) {
91674
+ const norm2 = this.normalizePath(dirPath);
91675
+ if (this.exists(norm2) !== "dir") return false;
91676
+ if (recursive) {
91677
+ const prefix = norm2 + "/";
91678
+ for (const key of [...this.files.keys()]) {
91679
+ if (key.startsWith(prefix)) {
91680
+ this.deleteFile(key);
91681
+ }
91682
+ }
91683
+ for (const dir of [...this.directories]) {
91684
+ if (dir === norm2 || dir.startsWith(prefix)) {
91685
+ this.directories.delete(dir);
91686
+ }
91687
+ }
91688
+ } else {
91689
+ const prefix = norm2 + "/";
91690
+ for (const key of this.files.keys()) {
91691
+ if (key.startsWith(prefix)) return false;
91692
+ }
91693
+ this.directories.delete(norm2);
91694
+ }
91695
+ return true;
91696
+ }
91697
+ /** List entries in a directory. */
91698
+ listDir(dirPath) {
91699
+ const norm2 = this.normalizePath(dirPath);
91700
+ const prefix = norm2 === "/" ? "/" : norm2 + "/";
91701
+ const results = [];
91702
+ const now = Date.now();
91703
+ results.push({
91704
+ name: ".",
91705
+ folder: norm2,
91706
+ bytes: 0,
91707
+ isdir: true,
91708
+ mtimeMs: now
91709
+ });
91710
+ results.push({
91711
+ name: "..",
91712
+ folder: norm2,
91713
+ bytes: 0,
91714
+ isdir: true,
91715
+ mtimeMs: now
91716
+ });
91717
+ const seen = /* @__PURE__ */ new Set();
91718
+ for (const [path, file] of this.files) {
91719
+ if (!path.startsWith(prefix)) continue;
91720
+ const rest = path.slice(prefix.length);
91721
+ const slashIdx = rest.indexOf("/");
91722
+ if (slashIdx === -1) {
91723
+ results.push({
91724
+ name: rest,
91725
+ folder: norm2,
91726
+ bytes: file.content.length,
91727
+ isdir: false,
91728
+ mtimeMs: file.mtimeMs
91729
+ });
91730
+ } else {
91731
+ const dirName = rest.slice(0, slashIdx);
91732
+ if (!seen.has(dirName)) {
91733
+ seen.add(dirName);
91734
+ results.push({
91735
+ name: dirName,
91736
+ folder: norm2,
91737
+ bytes: 0,
91738
+ isdir: true,
91739
+ mtimeMs: now
91740
+ });
91741
+ }
91742
+ }
91743
+ }
91744
+ for (const dir of this.directories) {
91745
+ if (!dir.startsWith(prefix)) continue;
91746
+ const rest = dir.slice(prefix.length);
91747
+ if (!rest.includes("/") && rest.length > 0 && !seen.has(rest)) {
91748
+ seen.add(rest);
91749
+ results.push({
91750
+ name: rest,
91751
+ folder: norm2,
91752
+ bytes: 0,
91753
+ isdir: true,
91754
+ mtimeMs: now
91755
+ });
91756
+ }
91757
+ }
91758
+ return results;
91759
+ }
91760
+ /** List all file paths in the VFS. */
91761
+ allFiles() {
91762
+ return [...this.files.keys()];
91763
+ }
91764
+ /** Get the changes since the VFS was created. */
91765
+ getChanges() {
91766
+ const created = [];
91767
+ const modified = [];
91768
+ for (const path of this.createdFiles) {
91769
+ const file = this.files.get(path);
91770
+ if (file) created.push({ path, content: file.content });
91771
+ }
91772
+ for (const path of this.modifiedFiles) {
91773
+ const file = this.files.get(path);
91774
+ if (file) modified.push({ path, content: file.content });
91775
+ }
91776
+ return {
91777
+ created,
91778
+ modified,
91779
+ deleted: [...this.deletedFiles]
91780
+ };
91781
+ }
91782
+ ensureParentDirs(path) {
91783
+ const parts = path.split("/").filter(Boolean);
91784
+ for (let i = 1; i < parts.length; i++) {
91785
+ const dir = "/" + parts.slice(0, i).join("/");
91786
+ this.directories.add(dir);
91787
+ }
91788
+ }
91789
+ };
91790
+
91791
+ // src/vfs/BrowserFileIOAdapter.ts
91792
+ import { unzipSync } from "fflate";
91793
+ var TEXT_DECODER = new TextDecoder("utf-8");
91794
+ var TEXT_ENCODER = new TextEncoder();
91795
+ var READ_CHUNK_SIZE = 8192;
91796
+ var BrowserFileIOAdapter = class {
91797
+ constructor(vfs) {
91798
+ this.vfs = vfs;
91799
+ }
91800
+ nextFid = 3;
91801
+ // 0=stdin, 1=stdout, 2=stderr reserved
91802
+ openFiles = /* @__PURE__ */ new Map();
91803
+ fopen(filename, permission) {
91804
+ const perm = permission.replace(/b/g, "");
91805
+ const path = this.vfs.normalizePath(filename);
91806
+ try {
91807
+ let data;
91808
+ let pos = 0;
91809
+ if (perm === "r" || perm === "r+") {
91810
+ if (this.vfs.exists(filename) !== "file") return -1;
91811
+ data = new Uint8Array(this.vfs.readFile(filename));
91812
+ } else if (perm === "w" || perm === "w+") {
91813
+ data = new Uint8Array(0);
91814
+ } else if (perm === "a" || perm === "a+") {
91815
+ if (this.vfs.exists(filename) === "file") {
91816
+ data = new Uint8Array(this.vfs.readFile(filename));
91817
+ pos = data.length;
91818
+ } else {
91819
+ data = new Uint8Array(0);
91820
+ }
91821
+ } else {
91822
+ return -1;
91823
+ }
91824
+ const fid = this.nextFid++;
91825
+ this.openFiles.set(fid, {
91826
+ path,
91827
+ permission: perm,
91828
+ lastError: "",
91829
+ buffer: "",
91830
+ eof: false,
91831
+ pos,
91832
+ data,
91833
+ dataLen: data.length,
91834
+ dirty: perm === "w" || perm === "a"
91835
+ // w starts dirty (truncated or new)
91836
+ });
91837
+ return fid;
91838
+ } catch {
91839
+ return -1;
91840
+ }
91841
+ }
91842
+ fclose(fidOrAll) {
91843
+ if (fidOrAll === "all") {
91844
+ for (const [, entry2] of this.openFiles) {
91845
+ this.flushEntry(entry2);
91846
+ }
91847
+ this.openFiles.clear();
91848
+ return 0;
91849
+ }
91850
+ const entry = this.openFiles.get(fidOrAll);
91851
+ if (!entry) return -1;
91852
+ this.flushEntry(entry);
91853
+ this.openFiles.delete(fidOrAll);
91854
+ return 0;
91855
+ }
91856
+ fgetl(fid) {
91857
+ const entry = this.getEntry(fid);
91858
+ if (!entry) return -1;
91859
+ return this.readLine(entry, false);
91860
+ }
91861
+ fgets(fid) {
91862
+ const entry = this.getEntry(fid);
91863
+ if (!entry) return -1;
91864
+ return this.readLine(entry, true);
91865
+ }
91866
+ fileread(filename) {
91867
+ const data = this.vfs.readFile(filename);
91868
+ return TEXT_DECODER.decode(data);
91869
+ }
91870
+ feof(fid) {
91871
+ const entry = this.getEntry(fid);
91872
+ if (!entry) return 1;
91873
+ if (entry.buffer.length > 0) return 0;
91874
+ if (entry.eof) return 1;
91875
+ if (entry.pos >= entry.dataLen) {
91876
+ entry.eof = true;
91877
+ return 1;
91878
+ }
91879
+ return 0;
91880
+ }
91881
+ ferror(fid) {
91882
+ const entry = this.getEntry(fid);
91883
+ if (!entry) return "Invalid file identifier";
91884
+ return entry.lastError;
91885
+ }
91886
+ fwrite(fid, text) {
91887
+ const entry = this.getEntry(fid);
91888
+ if (!entry) throw new Error(`Invalid file identifier: ${fid}`);
91889
+ const bytes = TEXT_ENCODER.encode(text);
91890
+ this.writeBytes(entry, bytes);
91891
+ entry.lastError = "";
91892
+ }
91893
+ freadBytes(fid, count) {
91894
+ const entry = this.getEntry(fid);
91895
+ if (!entry) throw new Error(`Invalid file identifier: ${fid}`);
91896
+ if (entry.buffer.length > 0) {
91897
+ entry.buffer = "";
91898
+ }
91899
+ const available = entry.dataLen - entry.pos;
91900
+ const toRead = Math.min(count, available);
91901
+ if (toRead <= 0) {
91902
+ entry.eof = true;
91903
+ return new Uint8Array(0);
91904
+ }
91905
+ const result = new Uint8Array(toRead);
91906
+ result.set(entry.data.subarray(entry.pos, entry.pos + toRead));
91907
+ entry.pos += toRead;
91908
+ if (entry.pos >= entry.dataLen) entry.eof = true;
91909
+ entry.lastError = "";
91910
+ return result;
91911
+ }
91912
+ fwriteBytes(fid, data) {
91913
+ const entry = this.getEntry(fid);
91914
+ if (!entry) throw new Error(`Invalid file identifier: ${fid}`);
91915
+ this.writeBytes(entry, data);
91916
+ entry.lastError = "";
91917
+ return data.length;
91918
+ }
91919
+ fseek(fid, offset, origin) {
91920
+ const entry = this.getEntry(fid);
91921
+ if (!entry) return -1;
91922
+ entry.buffer = "";
91923
+ entry.eof = false;
91924
+ if (origin === -1) {
91925
+ entry.pos = offset;
91926
+ } else if (origin === 0) {
91927
+ entry.pos += offset;
91928
+ } else {
91929
+ entry.pos = entry.dataLen + offset;
91930
+ }
91931
+ return 0;
91932
+ }
91933
+ ftell(fid) {
91934
+ const entry = this.getEntry(fid);
91935
+ if (!entry) return -1;
91936
+ return entry.pos;
91937
+ }
91938
+ scanDirectory(dirPath) {
91939
+ const norm2 = this.vfs.normalizePath(dirPath);
91940
+ const prefix = norm2 === "/" ? "/" : norm2 + "/";
91941
+ const results = [];
91942
+ for (const filePath of this.vfs.allFiles()) {
91943
+ if (!filePath.startsWith(prefix)) continue;
91944
+ const relativePath = filePath.slice(prefix.length);
91945
+ if (!relativePath) continue;
91946
+ const segments = relativePath.split("/");
91947
+ const dirs = segments.slice(0, -1);
91948
+ const allSpecial = dirs.every(
91949
+ (d) => d.startsWith("@") || d.startsWith("+") || d === "private"
91950
+ );
91951
+ if (!allSpecial) continue;
91952
+ if (!relativePath.endsWith(".m") && !relativePath.endsWith(".numbl.js") && !relativePath.endsWith(".wasm")) {
91953
+ continue;
91954
+ }
91955
+ try {
91956
+ const content = this.vfs.readFile(filePath);
91957
+ if (relativePath.endsWith(".wasm")) {
91958
+ results.push({
91959
+ name: filePath,
91960
+ source: "",
91961
+ data: new Uint8Array(content)
91962
+ });
91963
+ } else {
91964
+ results.push({
91965
+ name: filePath,
91966
+ source: TEXT_DECODER.decode(content)
91967
+ });
91968
+ }
91969
+ } catch {
91970
+ }
91971
+ }
91972
+ return results;
91973
+ }
91974
+ resolvePath(dirPath) {
91975
+ return this.vfs.normalizePath(dirPath);
91976
+ }
91977
+ existsPath(path) {
91978
+ return this.vfs.exists(path);
91979
+ }
91980
+ mkdir(dirPath) {
91981
+ return this.vfs.mkdir(dirPath);
91982
+ }
91983
+ tempdir() {
91984
+ return "/tmp";
91985
+ }
91986
+ userpath() {
91987
+ return "/system";
91988
+ }
91989
+ deleteFile(pattern) {
91990
+ const norm2 = this.vfs.normalizePath(pattern);
91991
+ if (norm2.includes("*") || norm2.includes("?")) {
91992
+ const lastSlash = norm2.lastIndexOf("/");
91993
+ const dir = norm2.slice(0, lastSlash);
91994
+ const globPat = norm2.slice(lastSlash + 1);
91995
+ const re = new RegExp(
91996
+ "^" + globPat.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".") + "$"
91997
+ );
91998
+ const entries = this.vfs.listDir(dir);
91999
+ for (const entry of entries) {
92000
+ if (!entry.isdir && re.test(entry.name)) {
92001
+ this.vfs.deleteFile(dir + "/" + entry.name);
92002
+ }
92003
+ }
92004
+ } else {
92005
+ this.vfs.deleteFile(norm2);
92006
+ }
92007
+ }
92008
+ rmdir(dirPath, recursive) {
92009
+ return this.vfs.rmdir(dirPath, recursive);
92010
+ }
92011
+ movefile(source, destination) {
92012
+ return this.vfs.movefile(source, destination);
92013
+ }
92014
+ copyfile(source, destination) {
92015
+ return this.vfs.copyfile(source, destination);
92016
+ }
92017
+ fileattrib(path) {
92018
+ return this.vfs.fileattrib(path);
92019
+ }
92020
+ listDir(dirPath) {
92021
+ const norm2 = this.vfs.normalizePath(dirPath);
92022
+ if (dirPath.includes("**")) {
92023
+ return this.listDirRecursive(dirPath);
92024
+ }
92025
+ if (dirPath.includes("*") || dirPath.includes("?")) {
92026
+ return this.listDirGlob(dirPath);
92027
+ }
92028
+ const type = this.vfs.exists(dirPath);
92029
+ if (type === "file") {
92030
+ const lastSlash = norm2.lastIndexOf("/");
92031
+ const name = lastSlash >= 0 ? norm2.slice(lastSlash + 1) : norm2;
92032
+ const folder = lastSlash >= 0 ? norm2.slice(0, lastSlash) : "/";
92033
+ return [
92034
+ {
92035
+ name,
92036
+ folder,
92037
+ bytes: this.vfs.fileSize(dirPath),
92038
+ isdir: false,
92039
+ mtimeMs: Date.now()
92040
+ }
92041
+ ];
92042
+ }
92043
+ return this.vfs.listDir(dirPath);
92044
+ }
92045
+ websave(url, filename, options) {
92046
+ url = filterUrl(url);
92047
+ const method = options?.requestMethod?.toUpperCase() || "GET";
92048
+ const xhr = new XMLHttpRequest();
92049
+ xhr.open(method, url, false);
92050
+ if (options?.timeout) xhr.timeout = Math.round(options.timeout * 1e3);
92051
+ if (options?.username && options?.password) {
92052
+ xhr.setRequestHeader(
92053
+ "Authorization",
92054
+ "Basic " + btoa(`${options.username}:${options.password}`)
92055
+ );
92056
+ }
92057
+ if (options?.keyName && options?.keyValue) {
92058
+ xhr.setRequestHeader(options.keyName, options.keyValue);
92059
+ }
92060
+ xhr.responseType = "arraybuffer";
92061
+ xhr.send();
92062
+ if (xhr.status < 200 || xhr.status >= 300) {
92063
+ throw new Error(`websave: HTTP ${xhr.status} for ${url}`);
92064
+ }
92065
+ const data = new Uint8Array(xhr.response);
92066
+ this.vfs.writeFile(filename, data);
92067
+ }
92068
+ webread(url, options) {
92069
+ url = filterUrl(url);
92070
+ const method = options?.requestMethod?.toUpperCase() || "GET";
92071
+ const xhr = new XMLHttpRequest();
92072
+ xhr.open(method, url, false);
92073
+ if (options?.timeout) xhr.timeout = Math.round(options.timeout * 1e3);
92074
+ if (options?.username && options?.password) {
92075
+ xhr.setRequestHeader(
92076
+ "Authorization",
92077
+ "Basic " + btoa(`${options.username}:${options.password}`)
92078
+ );
92079
+ }
92080
+ if (options?.keyName && options?.keyValue) {
92081
+ xhr.setRequestHeader(options.keyName, options.keyValue);
92082
+ }
92083
+ xhr.send();
92084
+ if (xhr.status < 200 || xhr.status >= 300) {
92085
+ throw new Error(`webread: HTTP ${xhr.status} for ${url}`);
92086
+ }
92087
+ return xhr.responseText;
92088
+ }
92089
+ unzip(zipfilename, outputfolder) {
92090
+ zipfilename = filterUrl(zipfilename);
92091
+ const zipData = this.vfs.readFile(zipfilename);
92092
+ this.vfs.mkdir(outputfolder);
92093
+ const files = unzipSync(zipData);
92094
+ const extracted = [];
92095
+ for (const [entryName, fileData] of Object.entries(files)) {
92096
+ if (entryName.endsWith("/")) {
92097
+ this.vfs.mkdir(outputfolder + "/" + entryName);
92098
+ continue;
92099
+ }
92100
+ const outPath = outputfolder + "/" + entryName;
92101
+ const lastSlash = outPath.lastIndexOf("/");
92102
+ if (lastSlash > 0) {
92103
+ this.vfs.mkdir(outPath.slice(0, lastSlash));
92104
+ }
92105
+ this.vfs.writeFile(outPath, fileData);
92106
+ extracted.push(this.vfs.normalizePath(outPath));
92107
+ }
92108
+ return extracted;
92109
+ }
92110
+ /** Get the VFS changes for syncing back to the main thread. */
92111
+ getChanges() {
92112
+ for (const [, entry] of this.openFiles) {
92113
+ this.flushEntry(entry);
92114
+ }
92115
+ return this.vfs.getChanges();
92116
+ }
92117
+ getEntry(fid) {
92118
+ return this.openFiles.get(fid);
92119
+ }
92120
+ flushEntry(entry) {
92121
+ if (entry.dirty) {
92122
+ const finalData = entry.dataLen === entry.data.length ? entry.data : entry.data.subarray(0, entry.dataLen);
92123
+ this.vfs.writeFile(entry.path, finalData);
92124
+ entry.dirty = false;
92125
+ }
92126
+ }
92127
+ writeBytes(entry, bytes) {
92128
+ const needed = entry.pos + bytes.length;
92129
+ if (needed > entry.data.length) {
92130
+ const newSize = Math.max(needed, entry.data.length * 2, 256);
92131
+ const newData = new Uint8Array(newSize);
92132
+ newData.set(entry.data.subarray(0, entry.dataLen));
92133
+ entry.data = newData;
92134
+ }
92135
+ entry.data.set(bytes, entry.pos);
92136
+ entry.pos += bytes.length;
92137
+ if (entry.pos > entry.dataLen) {
92138
+ entry.dataLen = entry.pos;
92139
+ }
92140
+ entry.dirty = true;
92141
+ }
92142
+ readLine(entry, keepNewline) {
92143
+ while (!entry.eof) {
92144
+ const nlIdx2 = entry.buffer.indexOf("\n");
92145
+ if (nlIdx2 !== -1) break;
92146
+ const available = entry.dataLen - entry.pos;
92147
+ if (available <= 0) {
92148
+ entry.eof = true;
92149
+ break;
92150
+ }
92151
+ const toRead = Math.min(READ_CHUNK_SIZE, available);
92152
+ const chunk = entry.data.subarray(entry.pos, entry.pos + toRead);
92153
+ entry.pos += toRead;
92154
+ entry.buffer += TEXT_DECODER.decode(chunk, { stream: true });
92155
+ }
92156
+ if (entry.buffer.length === 0) {
92157
+ return -1;
92158
+ }
92159
+ const nlIdx = entry.buffer.indexOf("\n");
92160
+ if (nlIdx !== -1) {
92161
+ const line2 = keepNewline ? entry.buffer.slice(0, nlIdx + 1) : entry.buffer.slice(0, nlIdx);
92162
+ entry.buffer = entry.buffer.slice(nlIdx + 1);
92163
+ return line2;
92164
+ }
92165
+ const line = entry.buffer;
92166
+ entry.buffer = "";
92167
+ return line;
92168
+ }
92169
+ listDirGlob(pattern) {
92170
+ const norm2 = this.vfs.normalizePath(pattern);
92171
+ const lastSlash = norm2.lastIndexOf("/");
92172
+ const dir = lastSlash >= 0 ? norm2.slice(0, lastSlash) : "/";
92173
+ const globPat = lastSlash >= 0 ? norm2.slice(lastSlash + 1) : norm2;
92174
+ const re = new RegExp(
92175
+ "^" + globPat.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".") + "$"
92176
+ );
92177
+ const entries = this.vfs.listDir(dir);
92178
+ return entries.filter(
92179
+ (e) => e.name !== "." && e.name !== ".." && re.test(e.name)
92180
+ );
92181
+ }
92182
+ listDirRecursive(pattern) {
92183
+ const idx = pattern.indexOf("**");
92184
+ let baseDir = pattern.slice(0, idx);
92185
+ if (baseDir.endsWith("/")) baseDir = baseDir.slice(0, -1);
92186
+ if (!baseDir) baseDir = ".";
92187
+ const results = [];
92188
+ const walkDir = (dir) => {
92189
+ const entries = this.vfs.listDir(dir);
92190
+ results.push(...entries);
92191
+ for (const entry of entries) {
92192
+ if (entry.isdir && entry.name !== "." && entry.name !== "..") {
92193
+ walkDir(entry.folder + "/" + entry.name);
92194
+ }
92195
+ }
92196
+ };
92197
+ walkDir(this.vfs.normalizePath(baseDir));
92198
+ return results;
92199
+ }
92200
+ };
92201
+ function filterUrl(url) {
92202
+ if (/^https:\/\/github\.com\/.+\/releases\/download\/.+/.test(url)) {
92203
+ url = url.replace(
92204
+ "https://github.com/",
92205
+ "https://mip-cors-proxy.figurl.workers.dev/gh/"
92206
+ );
92207
+ url += url.includes("?") ? "&" : "?";
92208
+ url += "t=" + Date.now();
92209
+ }
92210
+ return url;
92211
+ }
92212
+
92213
+ // src/vfs/BrowserSystemAdapter.ts
92214
+ var BrowserSystemAdapter = class {
92215
+ vars = /* @__PURE__ */ new Map();
92216
+ vfs = null;
92217
+ // Used only when no VFS is attached (e.g. tests that don't need files).
92218
+ fallbackCwd = "/";
92219
+ constructor(vfs) {
92220
+ if (vfs) this.vfs = vfs;
92221
+ }
92222
+ /** Attach (or replace) the VFS used as the cwd source of truth. */
92223
+ setVfs(vfs) {
92224
+ this.vfs = vfs;
92225
+ }
92226
+ getEnv(name) {
92227
+ return this.vars.get(name);
92228
+ }
92229
+ getAllEnv() {
92230
+ const result = {};
92231
+ for (const [k, v] of this.vars) {
92232
+ result[k] = v;
92233
+ }
92234
+ return result;
92235
+ }
92236
+ setEnv(name, value) {
92237
+ this.vars.set(name, value);
92238
+ }
92239
+ cwd() {
92240
+ return this.vfs ? this.vfs.getCwd() : this.fallbackCwd;
92241
+ }
92242
+ chdir(dir) {
92243
+ if (this.vfs) {
92244
+ this.vfs.setCwd(dir);
92245
+ } else {
92246
+ this.fallbackCwd = dir.startsWith("/") ? dir : this.fallbackCwd.replace(/\/$/, "") + "/" + dir;
92247
+ }
92248
+ }
92249
+ platform() {
92250
+ return "linux";
92251
+ }
92252
+ arch() {
92253
+ return "x64";
92254
+ }
92255
+ };
89195
92256
  export {
92257
+ BrowserFileIOAdapter,
92258
+ BrowserSystemAdapter,
89196
92259
  RTV,
89197
92260
  RuntimeError,
92261
+ VirtualFileSystem,
89198
92262
  displayValue,
89199
92263
  executeCode,
89200
92264
  loadQhullNodeBackend as loadDelaunayBackend,