numbl 0.4.6 → 0.4.7

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 (50) hide show
  1. package/dist-cli/cli.js +2363 -220
  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.js +2349 -216
  23. package/dist-lib/numbl-core/interpreter/builtins/gallery.d.ts +7 -0
  24. package/dist-lib/numbl-core/interpreter/builtins/geometry.d.ts +3 -3
  25. package/dist-lib/numbl-core/interpreter/builtins/graph.d.ts +29 -0
  26. package/dist-lib/numbl-core/interpreter/builtins/index.d.ts +2 -0
  27. package/dist-lib/numbl-core/interpreter/interpreter.d.ts +5 -0
  28. package/dist-lib/numbl-core/interpreter/interpreterExec.d.ts +14 -1
  29. package/dist-lib/numbl-core/interpreter/interpreterFunctions.d.ts +6 -0
  30. package/dist-lib/numbl-core/interpreter/interpreterSpecialBuiltins.d.ts +4 -0
  31. package/dist-lib/numbl-core/interpreter/types.d.ts +13 -0
  32. package/dist-lib/numbl-core/lowering/classInfo.d.ts +19 -2
  33. package/dist-lib/numbl-core/native/geometry-bridge.d.ts +10 -0
  34. package/dist-lib/numbl-core/parser/ArgumentsParser.d.ts +6 -1
  35. package/dist-lib/numbl-core/runtime/constructors.d.ts +2 -1
  36. package/dist-lib/numbl-core/runtime/runtime.d.ts +2 -1
  37. package/dist-lib/numbl-core/runtime/runtimeMemberAccess.d.ts +1 -1
  38. package/dist-lib/numbl-core/version.d.ts +1 -1
  39. package/dist-plot-viewer/assets/hdf5_hl-C9YUKPMe.js +24296 -0
  40. package/dist-plot-viewer/assets/index-YeXWXIxH.js +4504 -0
  41. package/dist-plot-viewer/index.html +1 -1
  42. package/dist-site-viewer/assets/hdf5_hl-C9YUKPMe.js +24296 -0
  43. package/dist-site-viewer/assets/index-RucHpf4b.js +4826 -0
  44. package/dist-site-viewer/assets/numbl-worker-IO39kohI.js +5228 -0
  45. package/dist-site-viewer/index.html +1 -1
  46. package/native/lapack_svd.cpp +50 -0
  47. package/package.json +21 -2
  48. package/dist-plot-viewer/assets/index-D4grNz8m.js +0 -4504
  49. package/dist-site-viewer/assets/index-B2mCFC55.js +0 -4826
  50. package/dist-site-viewer/assets/numbl-worker-DWZE29ck.js +0 -5116
package/dist-cli/cli.js CHANGED
@@ -320,6 +320,7 @@ import { homedir as homedir4 } from "os";
320
320
  import { fileURLToPath as fileURLToPath2 } from "url";
321
321
  import { createRequire } from "module";
322
322
  import { execSync as execSync2 } from "child_process";
323
+ import { createHash as createHash2 } from "crypto";
323
324
 
324
325
  // src/cli-plot-server.ts
325
326
  import { createServer } from "node:http";
@@ -2628,6 +2629,9 @@ var RTV = {
2628
2629
  }
2629
2630
  return new RuntimeClassInstance(className, fields, isHandleClass2);
2630
2631
  },
2632
+ classInstanceArray(className, elements, shape) {
2633
+ return new RuntimeClassInstanceArray(className, elements, shape);
2634
+ },
2631
2635
  complex(re, im) {
2632
2636
  return new RuntimeComplexNumber(re, im);
2633
2637
  },
@@ -21187,6 +21191,24 @@ function lu(data, m, n) {
21187
21191
  }
21188
21192
  function svd(data, m, n, econ, computeUV) {
21189
21193
  const k = Math.min(m, n);
21194
+ let finite = true;
21195
+ for (let i = 0; i < data.length; i++) {
21196
+ if (!Number.isFinite(data[i])) {
21197
+ finite = false;
21198
+ break;
21199
+ }
21200
+ }
21201
+ if (!finite) {
21202
+ const sNan = allocFloat64Array(k).fill(NaN);
21203
+ if (!computeUV) return { S: sNan };
21204
+ const uCols2 = econ ? k : m;
21205
+ const vCols2 = econ ? k : n;
21206
+ return {
21207
+ U: allocFloat64Array(m * uCols2).fill(NaN),
21208
+ S: sNan,
21209
+ V: allocFloat64Array(n * vCols2).fill(NaN)
21210
+ };
21211
+ }
21190
21212
  const a = allocFloat64Array(data);
21191
21213
  const s = allocFloat64Array(k);
21192
21214
  const JOBU_A2 = 0;
@@ -22413,8 +22435,8 @@ function indexIntoScalar(base, indices) {
22413
22435
  const idx = indices[0];
22414
22436
  if (isRuntimeTensor(idx)) {
22415
22437
  const is0x0 = idx.shape.length === 2 && idx.shape[0] === 0 && idx.shape[1] === 0;
22416
- const outShape = is0x0 ? [0, 0] : [...idx.shape];
22417
- return RTV.tensor(allocFloat64Array(0), outShape);
22438
+ const outShape2 = is0x0 ? [0, 0] : [...idx.shape];
22439
+ return RTV.tensor(allocFloat64Array(0), outShape2);
22418
22440
  }
22419
22441
  }
22420
22442
  if (indices.length === 1) {
@@ -22444,22 +22466,46 @@ function indexIntoScalar(base, indices) {
22444
22466
  if (k !== 1) throw new RuntimeError("Index exceeds array bounds");
22445
22467
  }
22446
22468
  const n = idx.data.length;
22447
- const scalarRe = isRuntimeNumber(base) ? base : base.re;
22448
- const data = allocFloat64Array(n);
22449
- data.fill(scalarRe);
22469
+ const scalarRe2 = isRuntimeNumber(base) ? base : base.re;
22470
+ const data2 = allocFloat64Array(n);
22471
+ data2.fill(scalarRe2);
22450
22472
  if (isRuntimeComplexNumber(base) && base.im !== 0) {
22451
22473
  const im = allocFloat64Array(n);
22452
22474
  im.fill(base.im);
22453
- return RTV.tensor(data, [...idx.shape], im);
22475
+ return RTV.tensor(data2, [...idx.shape], im);
22454
22476
  }
22455
- return RTV.tensor(data, [...idx.shape]);
22477
+ return RTV.tensor(data2, [...idx.shape]);
22456
22478
  }
22479
+ const dimLengths = [];
22480
+ let anyTensor = false;
22457
22481
  for (const idx of indices) {
22458
- if (isColonIndex(idx)) continue;
22459
- const i = toNumber(idx);
22460
- if (i !== 1) throw new RuntimeError("Index exceeds array bounds");
22482
+ if (isColonIndex(idx)) {
22483
+ dimLengths.push(1);
22484
+ } else if (isRuntimeTensor(idx)) {
22485
+ anyTensor = true;
22486
+ for (let i = 0; i < idx.data.length; i++) {
22487
+ if (Math.round(idx.data[i]) !== 1)
22488
+ throw new RuntimeError("Index exceeds array bounds");
22489
+ }
22490
+ dimLengths.push(idx.data.length);
22491
+ } else {
22492
+ if (Math.round(toNumber(idx)) !== 1)
22493
+ throw new RuntimeError("Index exceeds array bounds");
22494
+ dimLengths.push(1);
22495
+ }
22461
22496
  }
22462
- return base;
22497
+ if (!anyTensor) return base;
22498
+ const total = dimLengths.reduce((a, b) => a * b, 1);
22499
+ const scalarRe = isRuntimeNumber(base) ? base : base.re;
22500
+ const data = allocFloat64Array(total);
22501
+ data.fill(scalarRe);
22502
+ const outShape = dimLengths.length >= 2 ? dimLengths : [dimLengths[0], 1];
22503
+ if (isRuntimeComplexNumber(base) && base.im !== 0) {
22504
+ const im = allocFloat64Array(total);
22505
+ im.fill(base.im);
22506
+ return RTV.tensor(data, outShape, im);
22507
+ }
22508
+ return RTV.tensor(data, outShape);
22463
22509
  }
22464
22510
  function indexIntoTensor(base, indices) {
22465
22511
  if (indices.length === 1) {
@@ -22494,11 +22540,13 @@ function indexIntoTensor(base, indices) {
22494
22540
  function indexIntoTensor1D(base, idx) {
22495
22541
  if (isColonIndex(idx)) {
22496
22542
  const imag2 = base.imag ? allocFloat64Array(base.imag) : void 0;
22497
- return RTV.tensor(
22543
+ const result = RTV.tensor(
22498
22544
  allocFloat64Array(base.data),
22499
22545
  [base.data.length, 1],
22500
22546
  imag2
22501
22547
  );
22548
+ if (base._isLogical === true) result._isLogical = true;
22549
+ return result;
22502
22550
  }
22503
22551
  if (isRuntimeLogical(idx)) {
22504
22552
  if (!idx) return RTV.tensor(allocFloat64Array(0), [0, 0]);
@@ -22696,6 +22744,17 @@ function indexIntoChar(base, indices) {
22696
22744
  if (indices.length === 1) {
22697
22745
  const idx = indices[0];
22698
22746
  if (isColonIndex(idx)) return base;
22747
+ if (isRuntimeTensor(idx) && idx._isLogical) {
22748
+ let result = "";
22749
+ for (let k = 0; k < idx.data.length; k++) {
22750
+ if (idx.data[k] !== 0) {
22751
+ if (k >= base.value.length)
22752
+ throw new RuntimeError("Index exceeds char array length");
22753
+ result += base.value[k];
22754
+ }
22755
+ }
22756
+ return RTV.char(result);
22757
+ }
22699
22758
  if (isRuntimeTensor(idx)) {
22700
22759
  let result = "";
22701
22760
  for (let k = 0; k < idx.data.length; k++) {
@@ -22714,22 +22773,19 @@ function indexIntoChar(base, indices) {
22714
22773
  if (indices.length === 2) {
22715
22774
  const rowIdx = indices[0];
22716
22775
  const colIdx = indices[1];
22717
- let rows;
22718
- if (isColonIndex(rowIdx)) {
22719
- rows = Array.from({ length: nRows }, (_, i) => i);
22720
- } else if (isRuntimeTensor(rowIdx)) {
22721
- rows = Array.from(rowIdx.data, (v) => Math.round(v) - 1);
22722
- } else {
22723
- rows = [Math.round(toNumber(rowIdx)) - 1];
22724
- }
22725
- let cols;
22726
- if (isColonIndex(colIdx)) {
22727
- cols = Array.from({ length: nCols }, (_, i) => i);
22728
- } else if (isRuntimeTensor(colIdx)) {
22729
- cols = Array.from(colIdx.data, (v) => Math.round(v) - 1);
22730
- } else {
22731
- cols = [Math.round(toNumber(colIdx)) - 1];
22732
- }
22776
+ const toPositions = (idx) => {
22777
+ if (isRuntimeTensor(idx) && idx._isLogical) {
22778
+ const out = [];
22779
+ for (let i = 0; i < idx.data.length; i++)
22780
+ if (idx.data[i] !== 0) out.push(i);
22781
+ return out;
22782
+ }
22783
+ if (isRuntimeTensor(idx))
22784
+ return Array.from(idx.data, (v) => Math.round(v) - 1);
22785
+ return [Math.round(toNumber(idx)) - 1];
22786
+ };
22787
+ const rows = isColonIndex(rowIdx) ? Array.from({ length: nRows }, (_, i) => i) : toPositions(rowIdx);
22788
+ const cols = isColonIndex(colIdx) ? Array.from({ length: nCols }, (_, i) => i) : toPositions(colIdx);
22733
22789
  let result = "";
22734
22790
  for (const r of rows) {
22735
22791
  for (const c of cols) {
@@ -22755,11 +22811,11 @@ function indexIntoLogical(base, indices) {
22755
22811
  const vi = Math.round(idx.data[i2]);
22756
22812
  if (vi !== 1) throw new RuntimeError("Index exceeds array bounds");
22757
22813
  }
22758
- const data = allocFloat64Array(idx.data.length);
22759
- data.fill(base ? 1 : 0);
22760
- const result = RTV.tensor(data, [1, idx.data.length]);
22761
- result._isLogical = true;
22762
- return result;
22814
+ const data2 = allocFloat64Array(idx.data.length);
22815
+ data2.fill(base ? 1 : 0);
22816
+ const result2 = RTV.tensor(data2, [...idx.shape]);
22817
+ result2._isLogical = true;
22818
+ return result2;
22763
22819
  }
22764
22820
  if (isRuntimeLogical(idx)) {
22765
22821
  if (!idx) return RTV.tensor(allocFloat64Array(0), [0, 0]);
@@ -22769,16 +22825,35 @@ function indexIntoLogical(base, indices) {
22769
22825
  if (i !== 1) throw new RuntimeError("Index exceeds array bounds");
22770
22826
  return base;
22771
22827
  }
22828
+ const dimLengths = [];
22829
+ let anyTensor = false;
22772
22830
  for (const idx of indices) {
22773
- if (isColonIndex(idx)) continue;
22774
- if (isRuntimeLogical(idx)) {
22831
+ if (isColonIndex(idx)) {
22832
+ dimLengths.push(1);
22833
+ } else if (isRuntimeLogical(idx)) {
22775
22834
  if (!idx) return RTV.tensor(allocFloat64Array(0), [0, 0]);
22776
- continue;
22835
+ dimLengths.push(1);
22836
+ } else if (isRuntimeTensor(idx)) {
22837
+ anyTensor = true;
22838
+ for (let i = 0; i < idx.data.length; i++) {
22839
+ if (Math.round(idx.data[i]) !== 1)
22840
+ throw new RuntimeError("Index exceeds array bounds");
22841
+ }
22842
+ dimLengths.push(idx.data.length);
22843
+ } else {
22844
+ if (Math.round(toNumber(idx)) !== 1)
22845
+ throw new RuntimeError("Index exceeds array bounds");
22846
+ dimLengths.push(1);
22777
22847
  }
22778
- const i = Math.round(toNumber(idx));
22779
- if (i !== 1) throw new RuntimeError("Index exceeds array bounds");
22780
22848
  }
22781
- return base;
22849
+ if (!anyTensor) return base;
22850
+ const total = dimLengths.reduce((a, b) => a * b, 1);
22851
+ const data = allocFloat64Array(total);
22852
+ data.fill(base ? 1 : 0);
22853
+ const outShape = dimLengths.length >= 2 ? dimLengths : [dimLengths[0], 1];
22854
+ const result = RTV.tensor(data, outShape);
22855
+ result._isLogical = true;
22856
+ return result;
22782
22857
  }
22783
22858
  function indexIntoTensorWithTensor(base, idx) {
22784
22859
  const baseLogical = base._isLogical === true;
@@ -22858,11 +22933,15 @@ function indexIntoRTValue(base, indices) {
22858
22933
  return indexIntoLogical(base, indices);
22859
22934
  }
22860
22935
  if (isRuntimeStruct(base) || isRuntimeClassInstance(base)) {
22861
- if (indices.length === 1) {
22862
- const i = Math.round(toNumber(indices[0]));
22863
- if (i !== 1) throw new RuntimeError("Index exceeds struct dimensions");
22864
- return base;
22865
- }
22936
+ const allOnes = indices.length >= 1 && indices.every((idx) => {
22937
+ if (isRuntimeNumber(idx)) return Math.round(idx) === 1;
22938
+ if (isRuntimeLogical(idx)) return idx === true;
22939
+ if (isRuntimeTensor(idx))
22940
+ return idx.data.length === 1 && Math.round(idx.data[0]) === 1;
22941
+ return false;
22942
+ });
22943
+ if (allOnes) return base;
22944
+ throw new RuntimeError("Index exceeds struct dimensions");
22866
22945
  }
22867
22946
  if (isRuntimeSparseMatrix(base)) {
22868
22947
  return indexIntoSparse(base, indices);
@@ -22912,7 +22991,13 @@ function storeIntoTensor(base, indices, rhs, _rt) {
22912
22991
  }
22913
22992
  if (isShared(base)) {
22914
22993
  const cowImag = base.imag ? allocFloat64Array(base.imag) : void 0;
22915
- base = RTV.tensor(allocFloat64Array(base.data), [...base.shape], cowImag);
22994
+ const copy = RTV.tensor(
22995
+ allocFloat64Array(base.data),
22996
+ [...base.shape],
22997
+ cowImag
22998
+ );
22999
+ copy._isLogical = base._isLogical;
23000
+ base = copy;
22916
23001
  }
22917
23002
  if (indices.length === 1) {
22918
23003
  return storeIntoTensor1D(base, indices[0], rhs);
@@ -22940,7 +23025,9 @@ function deleteTensorElements(base, idx) {
22940
23025
  toDelete.add(Math.round(idx.data[i]) - 1);
22941
23026
  }
22942
23027
  } else if (isColonIndex(idx)) {
22943
- return RTV.tensor(allocFloat64Array(0), [0, 0]);
23028
+ const empty = RTV.tensor(allocFloat64Array(0), [0, 0]);
23029
+ empty._isLogical = base._isLogical;
23030
+ return empty;
22944
23031
  }
22945
23032
  if (toDelete.size === 0) return base;
22946
23033
  const newData = [];
@@ -22955,7 +23042,9 @@ function deleteTensorElements(base, idx) {
22955
23042
  const baseIsColVec = base.shape.length >= 2 && base.shape[1] === 1 && base.shape[0] !== 1;
22956
23043
  const outShape = baseIsColVec ? [newData.length, 1] : [1, newData.length];
22957
23044
  const imOut = hasImag && newIm.some((x) => x !== 0) ? allocFloat64Array(newIm) : void 0;
22958
- return RTV.tensor(allocFloat64Array(newData), outShape, imOut);
23045
+ const result = RTV.tensor(allocFloat64Array(newData), outShape, imOut);
23046
+ result._isLogical = base._isLogical;
23047
+ return result;
22959
23048
  }
22960
23049
  function collectDelIndices(idx, dimLen) {
22961
23050
  const s = /* @__PURE__ */ new Set();
@@ -22995,7 +23084,9 @@ function deleteTensorRowsOrCols(base, indices) {
22995
23084
  if (newIm && base.imag) newIm[dstIdx] = base.imag[srcIdx];
22996
23085
  }
22997
23086
  }
22998
- return RTV.tensor(newData, [newNrows, ncols], newIm);
23087
+ const result = RTV.tensor(newData, [newNrows, ncols], newIm);
23088
+ result._isLogical = base._isLogical;
23089
+ return result;
22999
23090
  }
23000
23091
  if (isColonIndex(indices[0])) {
23001
23092
  const delCols = collectDelIndices(indices[1], ncols);
@@ -23014,7 +23105,9 @@ function deleteTensorRowsOrCols(base, indices) {
23014
23105
  if (newIm && base.imag) newIm[dstIdx] = base.imag[srcIdx];
23015
23106
  }
23016
23107
  }
23017
- return RTV.tensor(newData, [nrows, newNcols], newIm);
23108
+ const result = RTV.tensor(newData, [nrows, newNcols], newIm);
23109
+ result._isLogical = base._isLogical;
23110
+ return result;
23018
23111
  }
23019
23112
  throw new RuntimeError("Cannot delete from both row and column dimensions");
23020
23113
  }
@@ -23084,6 +23177,9 @@ function storeIntoTensor1D(base, idx, rhs) {
23084
23177
  }
23085
23178
  function storeIntoTensorByVector(base, idx, rhs) {
23086
23179
  if (idx._isLogical) {
23180
+ if (isRuntimeTensor(rhs) && rhs.data.length === 1) {
23181
+ rhs = rhs.imag && rhs.imag[0] !== 0 ? RTV.complex(rhs.data[0], rhs.imag[0]) : RTV.num(rhs.data[0]);
23182
+ }
23087
23183
  const { re: rhsRe, im: rhsIm } = isRuntimeTensor(rhs) ? { re: null, im: null } : toReIm(rhs);
23088
23184
  let maxTruthy = -1;
23089
23185
  for (let i = 0; i < idx.data.length; i++) {
@@ -23333,7 +23429,8 @@ function storeIntoTensorND(base, indices, rhs) {
23333
23429
  const dimIndices = indices.map((idx, dim) => {
23334
23430
  const dimSize = dim < shape.length ? shape[dim] : 1;
23335
23431
  if (isColonIndex(idx)) {
23336
- if (dimSize === 0 && rhsShape) {
23432
+ const dimSizeUnknown = dimSize === 0 || dim >= shape.length;
23433
+ if (dimSizeUnknown && rhsShape) {
23337
23434
  const rDim = rhsDimCursor < rhsShape.length ? rhsShape[rhsDimCursor] : 1;
23338
23435
  rhsDimCursor++;
23339
23436
  return Array.from({ length: rDim }, (_, i) => i);
@@ -23650,6 +23747,9 @@ function horzcat(...values) {
23650
23747
  if (values.some((v) => isRuntimeSparseMatrix(v))) {
23651
23748
  return sparseCatAlongDim(values, 1);
23652
23749
  }
23750
+ if (values.some((v) => isRuntimeCell(v))) {
23751
+ return cellCatAlongDim(values, 1);
23752
+ }
23653
23753
  if (values.some((v) => isRuntimeChar(v))) {
23654
23754
  let result = "";
23655
23755
  for (const v of values) {
@@ -23670,9 +23770,6 @@ function horzcat(...values) {
23670
23770
  }
23671
23771
  return RTV.string(result);
23672
23772
  }
23673
- if (values.some((v) => isRuntimeCell(v))) {
23674
- return cellCatAlongDim(values, 1);
23675
- }
23676
23773
  if (values.some((v) => isRuntimeStruct(v) || isRuntimeStructArray(v))) {
23677
23774
  return structCat(values);
23678
23775
  }
@@ -23684,12 +23781,12 @@ function vertcat(...values) {
23684
23781
  if (values.some((v) => isRuntimeSparseMatrix(v))) {
23685
23782
  return sparseCatAlongDim(values, 0);
23686
23783
  }
23687
- if (values.some((v) => isRuntimeChar(v))) {
23688
- return vertcatChars(values);
23689
- }
23690
23784
  if (values.some((v) => isRuntimeCell(v))) {
23691
23785
  return cellCatAlongDim(values, 0);
23692
23786
  }
23787
+ if (values.some((v) => isRuntimeChar(v))) {
23788
+ return vertcatChars(values);
23789
+ }
23693
23790
  if (values.some((v) => isRuntimeStruct(v) || isRuntimeStructArray(v))) {
23694
23791
  return structCat(values);
23695
23792
  }
@@ -24035,11 +24132,22 @@ function catAlongDim(values, dimIdx) {
24035
24132
  if (allLogical) result._isLogical = true;
24036
24133
  return result;
24037
24134
  }
24135
+ function isEmptyNonCellOperand(v) {
24136
+ if (isRuntimeChar(v)) return v.value.length === 0;
24137
+ if (isRuntimeTensor(v)) return v.data.length === 0;
24138
+ return false;
24139
+ }
24038
24140
  function cellCatAlongDim(values, dimIdx) {
24039
- let cells = values.map((v) => {
24040
- if (isRuntimeCell(v)) return v;
24041
- return RTV.cell([v], [1, 1]);
24042
- });
24141
+ let cells = [];
24142
+ for (const v of values) {
24143
+ if (isRuntimeCell(v)) {
24144
+ cells.push(v);
24145
+ } else if (isEmptyNonCellOperand(v)) {
24146
+ continue;
24147
+ } else {
24148
+ cells.push(RTV.cell([v], [1, 1]));
24149
+ }
24150
+ }
24043
24151
  cells = cells.filter((c) => c.data.length > 0);
24044
24152
  if (cells.length === 0) return RTV.cell([], [0, 0]);
24045
24153
  if (cells.length === 1) return cells[0];
@@ -27229,6 +27337,24 @@ function sprintfFormat(fmt, args) {
27229
27337
  case "t":
27230
27338
  result += " ";
27231
27339
  break;
27340
+ case "r":
27341
+ result += "\r";
27342
+ break;
27343
+ case "a":
27344
+ result += "\x07";
27345
+ break;
27346
+ case "b":
27347
+ result += "\b";
27348
+ break;
27349
+ case "f":
27350
+ result += "\f";
27351
+ break;
27352
+ case "v":
27353
+ result += "\v";
27354
+ break;
27355
+ case "0":
27356
+ result += "\0";
27357
+ break;
27232
27358
  case "\\":
27233
27359
  result += "\\";
27234
27360
  break;
@@ -31459,7 +31585,7 @@ var ExpressionParser = class extends ParserBase {
31459
31585
  }
31460
31586
  } else if (expr.type === "Ident" && this.peekToken() === 41 /* At */ && this.peekTokenAt(1) === 4 /* Ident */) {
31461
31587
  this.pos++;
31462
- const className = this.expectIdent();
31588
+ const className = this.parseQualifiedName();
31463
31589
  if (!this.consume(50 /* LParen */)) {
31464
31590
  throw this.error("expected '(' after superclass name in super call");
31465
31591
  }
@@ -32060,7 +32186,10 @@ var ControlFlowParser = class extends CommandParser {
32060
32186
  let catchBody = [];
32061
32187
  if (this.consume(27 /* Catch */)) {
32062
32188
  if (this.peekToken() === 4 /* Ident */) {
32063
- catchVar = this.expectIdent();
32189
+ const after = this.peekTokenAt(1);
32190
+ if (after === void 0 || after === 68 /* Newline */ || after === 49 /* Semicolon */ || after === 47 /* Comma */ || after === 15 /* End */) {
32191
+ catchVar = this.expectIdent();
32192
+ }
32064
32193
  }
32065
32194
  catchBody = this.parseBlock((t) => t === 15 /* End */);
32066
32195
  }
@@ -32185,8 +32314,12 @@ var ArgumentsParser = class extends ControlFlowParser {
32185
32314
  dimensions = this.parseArgDimensions();
32186
32315
  }
32187
32316
  let className = null;
32188
- if (this.peekToken() === 4 /* Ident */ && this.peekToken() !== 68 /* Newline */ && this.peekToken() !== 49 /* Semicolon */) {
32317
+ if (this.peekToken() === 4 /* Ident */) {
32189
32318
  className = this.next().lexeme;
32319
+ while (this.peekToken() === 45 /* Dot */) {
32320
+ this.consume(45 /* Dot */);
32321
+ className += "." + this.expectIdent();
32322
+ }
32190
32323
  }
32191
32324
  let validators = [];
32192
32325
  if (this.peekToken() === 54 /* LBrace */) {
@@ -32222,18 +32355,28 @@ var ArgumentsParser = class extends ControlFlowParser {
32222
32355
  return dims;
32223
32356
  }
32224
32357
  /**
32225
- * Parse validator list: {mustBeNumeric, mustBePositive}
32358
+ * Parse validator list: {mustBeNumeric, mustBePositive}. Validators may be
32359
+ * full function calls with arguments, e.g.
32360
+ * `{mustBeMember(x,["a","b"]), mustBeInRange(x,0,7)}`. The validator bodies
32361
+ * are not enforced at runtime, so we capture the top-level validator name
32362
+ * tokens and skip past any nested `(...)`, `[...]`, `{...}` so parsing
32363
+ * resumes correctly at the default value (`= expr`) or end of line.
32226
32364
  */
32227
32365
  parseArgValidators() {
32228
32366
  this.consume(54 /* LBrace */);
32229
32367
  const validators = [];
32230
- while (this.peekToken() !== 55 /* RBrace */ && this.peekToken() !== void 0) {
32231
- if (this.consume(47 /* Comma */)) continue;
32232
- if (this.peekToken() === 4 /* Ident */) {
32233
- validators.push(this.next().lexeme);
32234
- } else {
32235
- break;
32368
+ let depth = 0;
32369
+ while (this.peekToken() !== void 0) {
32370
+ const tok = this.peekToken();
32371
+ if (depth === 0 && tok === 55 /* RBrace */) break;
32372
+ if (tok === 50 /* LParen */ || tok === 52 /* LBracket */ || tok === 54 /* LBrace */) {
32373
+ depth++;
32374
+ } else if (tok === 51 /* RParen */ || tok === 53 /* RBracket */ || tok === 55 /* RBrace */) {
32375
+ depth--;
32376
+ } else if (depth === 0 && tok === 4 /* Ident */) {
32377
+ validators.push(this.peek().lexeme);
32236
32378
  }
32379
+ this.next();
32237
32380
  }
32238
32381
  this.consume(55 /* RBrace */);
32239
32382
  return validators;
@@ -32264,7 +32407,8 @@ var FunctionParser = class extends ArgumentsParser {
32264
32407
  if (this.consume(52 /* LBracket */)) {
32265
32408
  if (this.peekToken() !== 53 /* RBracket */) {
32266
32409
  outputs.push(this.expectIdentOrTilde());
32267
- while (this.consume(47 /* Comma */)) {
32410
+ while (this.peekToken() === 47 /* Comma */ || this.peekToken() === 4 /* Ident */ || this.peekToken() === 40 /* Tilde */) {
32411
+ this.consume(47 /* Comma */);
32268
32412
  outputs.push(this.expectIdentOrTilde());
32269
32413
  }
32270
32414
  }
@@ -32372,6 +32516,14 @@ var FunctionParser = class extends ArgumentsParser {
32372
32516
  };
32373
32517
 
32374
32518
  // src/numbl-core/parser/ClassParser.ts
32519
+ var HANDLE_BASE_CLASSES = /* @__PURE__ */ new Set([
32520
+ "handle",
32521
+ "dynamicprops",
32522
+ "matlab.mixin.Copyable",
32523
+ "matlab.mixin.SetGet",
32524
+ "matlab.mixin.SetGetExactNames",
32525
+ "hgsetget"
32526
+ ]);
32375
32527
  var ClassParser = class extends FunctionParser {
32376
32528
  // ── Imports & ClassDef ───────────────────────────────────────────────
32377
32529
  parseImport() {
@@ -32406,7 +32558,11 @@ var ClassParser = class extends FunctionParser {
32406
32558
  const name = this.parseQualifiedName();
32407
32559
  let superClass = null;
32408
32560
  if (this.consume(43 /* Less */)) {
32409
- superClass = this.parseQualifiedName();
32561
+ const supers = [this.parseQualifiedName()];
32562
+ while (this.consume(38 /* And */)) {
32563
+ supers.push(this.parseQualifiedName());
32564
+ }
32565
+ superClass = supers.some((s) => HANDLE_BASE_CLASSES.has(s)) ? "handle" : supers[0];
32410
32566
  }
32411
32567
  const members = [];
32412
32568
  while (true) {
@@ -33281,7 +33437,7 @@ function insertFunctionEndTokens(tokens) {
33281
33437
  }
33282
33438
  if (BLOCK_OPENERS.has(tok.token)) {
33283
33439
  depth++;
33284
- } else if (tok.token === 15 /* End */ && groupDepth === 0) {
33440
+ } else if (tok.token === 15 /* End */ && groupDepth === 0 && tokens[i - 1]?.token !== 45 /* Dot */) {
33285
33441
  depth--;
33286
33442
  if (depth === 0 && inFunction) {
33287
33443
  inFunction = false;
@@ -34806,7 +34962,9 @@ function dispatchPlotBuiltin(name, args, instructions, state) {
34806
34962
  return true;
34807
34963
  }
34808
34964
  case "close": {
34809
- if (args.length > 0 && toString(args[0]) === "all") {
34965
+ const a0 = args[0];
34966
+ const isText3 = a0 !== void 0 && (isRuntimeChar(a0) || isRuntimeString(a0));
34967
+ if (isText3 && toString(a0) === "all") {
34810
34968
  plotInstr(instructions, { type: "close_all" });
34811
34969
  } else {
34812
34970
  plotInstr(instructions, { type: "close" });
@@ -35414,43 +35572,40 @@ function methodDispatch(rt, name, nargout, args) {
35414
35572
  }
35415
35573
  }
35416
35574
  }
35417
- try {
35418
- return callClassMethod(rt, firstRV.className, name, nargout, args);
35419
- } catch (e) {
35420
- if (e instanceof RuntimeError) {
35421
- const guardKey = `${firstRV.className}.subsref`;
35422
- if (!rt.activeAccessors.has(guardKey)) {
35423
- const subsrefFn = rt.cachedResolveClassMethod(
35424
- firstRV.className,
35425
- "subsref"
35426
- );
35427
- if (subsrefFn) {
35428
- const remaining = args.slice(1);
35429
- const sEntries = [
35430
- RTV.struct({
35431
- type: RTV.char("."),
35432
- subs: RTV.char(name)
35433
- }),
35434
- RTV.struct({
35435
- type: RTV.char("()"),
35436
- subs: RTV.cell(
35437
- remaining.map((a) => ensureRuntimeValue(a)),
35438
- [1, remaining.length]
35439
- )
35440
- })
35441
- ];
35442
- const S = RTV.structArray(["type", "subs"], sEntries);
35443
- rt.activeAccessors.add(guardKey);
35444
- try {
35445
- return subsrefFn(nargout, first, S);
35446
- } finally {
35447
- rt.activeAccessors.delete(guardKey);
35448
- }
35575
+ const methodExists = rt.cachedResolveClassMethod(firstRV.className, name) !== null;
35576
+ if (!methodExists) {
35577
+ const guardKey = `${firstRV.className}.subsref`;
35578
+ if (!rt.activeAccessors.has(guardKey)) {
35579
+ const subsrefFn = rt.cachedResolveClassMethod(
35580
+ firstRV.className,
35581
+ "subsref"
35582
+ );
35583
+ if (subsrefFn) {
35584
+ const remaining = args.slice(1);
35585
+ const sEntries = [
35586
+ RTV.struct({
35587
+ type: RTV.char("."),
35588
+ subs: RTV.char(name)
35589
+ }),
35590
+ RTV.struct({
35591
+ type: RTV.char("()"),
35592
+ subs: RTV.cell(
35593
+ remaining.map((a) => ensureRuntimeValue(a)),
35594
+ [1, remaining.length]
35595
+ )
35596
+ })
35597
+ ];
35598
+ const S = RTV.structArray(["type", "subs"], sEntries);
35599
+ rt.activeAccessors.add(guardKey);
35600
+ try {
35601
+ return subsrefFn(nargout, first, S);
35602
+ } finally {
35603
+ rt.activeAccessors.delete(guardKey);
35449
35604
  }
35450
35605
  }
35451
35606
  }
35452
- throw e;
35453
35607
  }
35608
+ return callClassMethod(rt, firstRV.className, name, nargout, args);
35454
35609
  }
35455
35610
  }
35456
35611
  const builtin = rt.builtins[name];
@@ -35507,11 +35662,13 @@ function arrayfunCellfunImpl(rt, name, nargout, args) {
35507
35662
  const getElem = (arr, i) => {
35508
35663
  if (isRuntimeCell(arr)) return arr.data[i];
35509
35664
  if (isRuntimeTensor(arr)) return arr.data[i];
35665
+ if (isRuntimeStructArray(arr)) return arr.elements[i];
35510
35666
  return arr;
35511
35667
  };
35512
35668
  const getLen = (arr) => {
35513
35669
  if (isRuntimeCell(arr)) return arr.data.length;
35514
35670
  if (isRuntimeTensor(arr)) return arr.data.length;
35671
+ if (isRuntimeStructArray(arr)) return arr.elements.length;
35515
35672
  return 1;
35516
35673
  };
35517
35674
  const collectArgs = (i) => {
@@ -35547,9 +35704,9 @@ function arrayfunCellfunImpl(rt, name, nargout, args) {
35547
35704
  if (allLogical && arrArg.data.length > 0) result._isLogical = true;
35548
35705
  return result;
35549
35706
  }
35550
- if (isRuntimeCell(arrArg) || extraInputs.length > 0 || nargout > 1) {
35707
+ if (isRuntimeCell(arrArg) || isRuntimeStructArray(arrArg) || extraInputs.length > 0 || nargout > 1) {
35551
35708
  const len = getLen(arrArg);
35552
- const shape = isRuntimeCell(arrArg) ? [...arrArg.shape] : isRuntimeTensor(arrArg) ? [...arrArg.shape] : [1, len];
35709
+ const shape = isRuntimeCell(arrArg) || isRuntimeTensor(arrArg) ? [...arrArg.shape] : [1, len];
35553
35710
  if (nargout > 1) {
35554
35711
  const allResults = Array.from(
35555
35712
  { length: nargout },
@@ -36770,6 +36927,28 @@ function indexStore(rt, base, indices, rhs, skipSubsasgn = false) {
36770
36927
  if (isRuntimeStruct(mv)) {
36771
36928
  return ensureRuntimeValue(rhs);
36772
36929
  }
36930
+ if (indices.length === 1 && isRuntimeClassInstance(ensureRuntimeValue(rhs)) && // Object-array growth uses a numeric position. A char/string index (e.g. a
36931
+ // key into a containers.Map whose value happens to be a class instance)
36932
+ // must fall through to the subsasgn dispatch below, not be coerced to a
36933
+ // number — `toNumber` on a multi-char value throws.
36934
+ !isRuntimeChar(ensureRuntimeValue(indices[0]))) {
36935
+ const k = Math.round(toNumber(ensureRuntimeValue(indices[0]))) - 1;
36936
+ const growsArray = isRuntimeClassInstanceArray(mv) || isRuntimeTensor(mv) && mv.data.length === 0 || isRuntimeClassInstance(mv) && k >= 1;
36937
+ if (growsArray) {
36938
+ if (k < 0) throw new RuntimeError("Index must be a positive integer");
36939
+ const rhsInst = ensureRuntimeValue(rhs);
36940
+ const existing = isRuntimeClassInstanceArray(mv) ? [...mv.elements] : isRuntimeClassInstance(mv) ? [mv] : [];
36941
+ const className = isRuntimeClassInstanceArray(mv) ? mv.className : isRuntimeClassInstance(mv) ? mv.className : rhsInst.className;
36942
+ while (existing.length < k) {
36943
+ existing.push(
36944
+ RTV.classInstance(className, [...rhsInst.fields.keys()], false)
36945
+ );
36946
+ }
36947
+ existing[k] = rhsInst;
36948
+ if (existing.length === 1) return existing[0];
36949
+ return RTV.classInstanceArray(className, existing, [1, existing.length]);
36950
+ }
36951
+ }
36773
36952
  if (isRuntimeClassInstance(mv)) {
36774
36953
  const guardKey = `${mv.className}.subsasgn`;
36775
36954
  if (!skipSubsasgn && !rt.activeAccessors.has(guardKey)) {
@@ -37190,6 +37369,11 @@ function validateDim(x) {
37190
37369
  throw new RuntimeError("Size inputs must be nonnegative integers.");
37191
37370
  return Math.max(0, x);
37192
37371
  }
37372
+ function toScalarDim(v) {
37373
+ if (isRuntimeTensor(v) && v.data.length !== 1)
37374
+ throw new RuntimeError("Size inputs must be scalar.");
37375
+ return validateDim(toNumber(v));
37376
+ }
37193
37377
  function parseShapeArgs(args) {
37194
37378
  if (args.length === 1 && isRuntimeTensor(args[0])) {
37195
37379
  const t = args[0];
@@ -37197,7 +37381,7 @@ function parseShapeArgs(args) {
37197
37381
  for (let i = 0; i < t.data.length; i++) shape.push(validateDim(t.data[i]));
37198
37382
  return shape;
37199
37383
  }
37200
- return args.map((a) => validateDim(toNumber(a)));
37384
+ return args.map(toScalarDim);
37201
37385
  }
37202
37386
  function arrayConstructorCases(fillFn, scalarValue, opts) {
37203
37387
  const cases = [
@@ -37238,12 +37422,14 @@ function arrayConstructorCases(fillFn, scalarValue, opts) {
37238
37422
  return fillFn(shape);
37239
37423
  }
37240
37424
  },
37241
- // Multiple scalar args
37425
+ // Multiple scalar args. A 1×1 array counts as a scalar dimension, so
37426
+ // accept `tensor` here too (apply-time validates each is scalar).
37242
37427
  {
37243
37428
  match: (argTypes) => {
37244
37429
  if (argTypes.length <= 1) return null;
37245
37430
  for (const a of argTypes) {
37246
- if (a.kind !== "number" && a.kind !== "boolean") return null;
37431
+ if (a.kind !== "number" && a.kind !== "boolean" && a.kind !== "tensor")
37432
+ return null;
37247
37433
  }
37248
37434
  const shape = argTypes.map(
37249
37435
  (a) => a.kind === "number" && typeof a.exact === "number" ? a.exact : -1
@@ -37803,6 +37989,13 @@ registerIBuiltin({
37803
37989
  apply: (_args, nargout) => nargout >= 1 ? RTV.dummyHandle() : void 0
37804
37990
  })
37805
37991
  });
37992
+ registerIBuiltin({
37993
+ name: "waitbar",
37994
+ resolve: () => ({
37995
+ outputTypes: [{ kind: "unknown" }],
37996
+ apply: (_args, nargout) => nargout >= 1 ? RTV.dummyHandle() : void 0
37997
+ })
37998
+ });
37806
37999
  registerIBuiltin({
37807
38000
  name: "get",
37808
38001
  resolve: () => ({
@@ -39117,8 +39310,8 @@ function registerSpecialBuiltins(rt) {
39117
39310
  if (args.length === 0) return nargout >= 1 ? RTV.num(0) : void 0;
39118
39311
  const margs = args.map((a) => ensureRuntimeValue(a));
39119
39312
  if (margs.length === 2 && isRuntimeChar(margs[0]) && isRuntimeChar(margs[1])) {
39120
- const state = toString(margs[0]);
39121
- if (state === "on" || state === "off") {
39313
+ const mode = toString(margs[0]);
39314
+ if (mode === "on" || mode === "off") {
39122
39315
  if (nargout === 0) return void 0;
39123
39316
  return RTV.struct(
39124
39317
  /* @__PURE__ */ new Map([
@@ -39127,6 +39320,14 @@ function registerSpecialBuiltins(rt) {
39127
39320
  ])
39128
39321
  );
39129
39322
  }
39323
+ if (mode === "query") {
39324
+ return RTV.struct(
39325
+ /* @__PURE__ */ new Map([
39326
+ ["state", RTV.char("on")],
39327
+ ["identifier", margs[1]]
39328
+ ])
39329
+ );
39330
+ }
39130
39331
  }
39131
39332
  let fmtIdx = 0;
39132
39333
  if (margs.length >= 2 && isRuntimeChar(margs[0]) && toString(margs[0]).includes(":")) {
@@ -40649,6 +40850,10 @@ function _eigsImpl(rt, nargout, args) {
40649
40850
  const margs = args.map((a) => ensureRuntimeValue(a));
40650
40851
  if (margs.length < 1)
40651
40852
  throw new RuntimeError("eigs requires at least 1 argument");
40853
+ for (let i = 0; i < margs.length; i++) {
40854
+ const mi = margs[i];
40855
+ if (isRuntimeSparseMatrix(mi)) margs[i] = sparseToDense(mi);
40856
+ }
40652
40857
  let afun = null;
40653
40858
  let A = null;
40654
40859
  let n;
@@ -41566,12 +41771,17 @@ function getMember(rt, base, name) {
41566
41771
  `No property or method '${name}' for class '${mv.className}'`
41567
41772
  );
41568
41773
  }
41774
+ if (isRuntimeClassInstanceArray(mv)) {
41775
+ const values = mv.elements.map(
41776
+ (el) => ensureRuntimeValue(getMember(rt, el, name))
41777
+ );
41778
+ return horzcat(...values);
41779
+ }
41569
41780
  return getRTValueField(mv, name);
41570
41781
  }
41571
- function getMemberDynamic(base, nameExpr) {
41572
- const mv = ensureRuntimeValue(base);
41782
+ function getMemberDynamic(rt, base, nameExpr) {
41573
41783
  const name = toString(ensureRuntimeValue(nameExpr));
41574
- return getRTValueField(mv, name);
41784
+ return ensureRuntimeValue(getMember(rt, base, name));
41575
41785
  }
41576
41786
  function getMemberOrEmpty(base, name) {
41577
41787
  try {
@@ -41593,7 +41803,7 @@ function setMemberReturn(rt, base, name, rhs) {
41593
41803
  if (setter) {
41594
41804
  rt.activeAccessors.add(accessorKey);
41595
41805
  try {
41596
- const result = setter(1, base, rhs);
41806
+ const result = setter(0, base, rhs);
41597
41807
  return result !== void 0 ? result : base;
41598
41808
  } finally {
41599
41809
  rt.activeAccessors.delete(accessorKey);
@@ -41605,10 +41815,8 @@ function setMemberReturn(rt, base, name, rhs) {
41605
41815
  return setRTValueField(mv, name, rhsMv, rt);
41606
41816
  }
41607
41817
  function setMemberDynamicReturn(rt, base, nameExpr, rhs) {
41608
- const mv = ensureRuntimeValue(base);
41609
41818
  const name = toString(ensureRuntimeValue(nameExpr));
41610
- const rhsMv = ensureRuntimeValue(rhs);
41611
- return setRTValueField(mv, name, rhsMv, rt);
41819
+ return ensureRuntimeValue(setMemberReturn(rt, base, name, rhs));
41612
41820
  }
41613
41821
  function subsrefCall(rt, base, names) {
41614
41822
  const mv = ensureRuntimeValue(base);
@@ -44257,7 +44465,7 @@ var Runtime = class _Runtime {
44257
44465
  return getMember(this, base, name);
44258
44466
  }
44259
44467
  getMemberDynamic(base, nameExpr) {
44260
- return getMemberDynamic(base, nameExpr);
44468
+ return getMemberDynamic(this, base, nameExpr);
44261
44469
  }
44262
44470
  getMemberOrEmpty(base, name) {
44263
44471
  return getMemberOrEmpty(base, name);
@@ -45564,6 +45772,8 @@ defineBuiltin({
45564
45772
  if (isRuntimeComplexNumber(v)) return v.im === 0;
45565
45773
  if (isRuntimeTensor(v)) return imagAllZero(v.imag);
45566
45774
  if (isRuntimeSparseMatrix(v)) return !v.pi || imagAllZero(v.pi);
45775
+ if (isRuntimeCell(v) || isRuntimeStruct(v) || isRuntimeStructArray(v) || isRuntimeString(v) || isRuntimeFunction(v))
45776
+ return false;
45567
45777
  return true;
45568
45778
  }
45569
45779
  }
@@ -46035,10 +46245,79 @@ defineBuiltin({
46035
46245
  name: "ischar",
46036
46246
  cases: [anyToLogicalCase((args) => isRuntimeChar(args[0]))]
46037
46247
  });
46248
+ defineBuiltin({
46249
+ name: "isstr",
46250
+ cases: [anyToLogicalCase((args) => isRuntimeChar(args[0]))]
46251
+ });
46038
46252
  defineBuiltin({
46039
46253
  name: "isstring",
46040
46254
  cases: [anyToLogicalCase((args) => isRuntimeString(args[0]))]
46041
46255
  });
46256
+ var MATLAB_KEYWORDS = /* @__PURE__ */ new Set([
46257
+ "break",
46258
+ "case",
46259
+ "catch",
46260
+ "classdef",
46261
+ "continue",
46262
+ "else",
46263
+ "elseif",
46264
+ "end",
46265
+ "for",
46266
+ "function",
46267
+ "global",
46268
+ "if",
46269
+ "otherwise",
46270
+ "parfor",
46271
+ "persistent",
46272
+ "return",
46273
+ "spmd",
46274
+ "switch",
46275
+ "try",
46276
+ "while"
46277
+ ]);
46278
+ function singleRowText(v) {
46279
+ if (isRuntimeChar(v)) {
46280
+ if (v.shape && v.shape.length >= 1 && v.shape[0] > 1) return null;
46281
+ return v.value;
46282
+ }
46283
+ if (isRuntimeString(v)) return v;
46284
+ return null;
46285
+ }
46286
+ defineBuiltin({
46287
+ name: "isvarname",
46288
+ cases: [
46289
+ anyToLogicalCase((args) => {
46290
+ const s = singleRowText(args[0]);
46291
+ if (s === null || s.length === 0 || s.length > 63) return false;
46292
+ if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(s)) return false;
46293
+ return !MATLAB_KEYWORDS.has(s);
46294
+ })
46295
+ ]
46296
+ });
46297
+ defineBuiltin({
46298
+ name: "iskeyword",
46299
+ cases: [
46300
+ {
46301
+ // iskeyword() with no args returns the list of keywords (column cell).
46302
+ match: (argTypes) => {
46303
+ if (argTypes.length === 0) return [{ kind: "cell" }];
46304
+ if (argTypes.length === 1) return [{ kind: "boolean" }];
46305
+ return null;
46306
+ },
46307
+ apply: (args) => {
46308
+ if (args.length === 0) {
46309
+ const names = [...MATLAB_KEYWORDS].sort();
46310
+ return RTV.cell(
46311
+ names.map((n) => RTV.char(n)),
46312
+ [names.length, 1]
46313
+ );
46314
+ }
46315
+ const s = singleRowText(args[0]);
46316
+ return RTV.logical(s !== null && MATLAB_KEYWORDS.has(s));
46317
+ }
46318
+ }
46319
+ ]
46320
+ });
46042
46321
  defineBuiltin({
46043
46322
  name: "iscell",
46044
46323
  cases: [anyToLogicalCase((args) => isRuntimeCell(args[0]))]
@@ -46055,6 +46334,82 @@ defineBuiltin({
46055
46334
  name: "issparse",
46056
46335
  cases: [anyToLogicalCase((args) => isRuntimeSparseMatrix(args[0]))]
46057
46336
  });
46337
+ defineBuiltin({
46338
+ name: "isobject",
46339
+ cases: [
46340
+ anyToLogicalCase(
46341
+ (args) => isRuntimeClassInstance(args[0]) || isRuntimeClassInstanceArray(args[0])
46342
+ )
46343
+ ]
46344
+ });
46345
+ defineBuiltin({
46346
+ name: "isprop",
46347
+ help: {
46348
+ signatures: ["tf = isprop(obj, PropertyName)"],
46349
+ 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."
46350
+ },
46351
+ cases: [
46352
+ {
46353
+ match: (argTypes) => {
46354
+ if (argTypes.length !== 2) return null;
46355
+ return [{ kind: "boolean" }];
46356
+ },
46357
+ apply: (args) => {
46358
+ const v = args[0];
46359
+ const nameArg = args[1];
46360
+ let propName = null;
46361
+ if (isRuntimeChar(nameArg)) propName = nameArg.value;
46362
+ else if (isRuntimeString(nameArg)) propName = nameArg;
46363
+ let answer = false;
46364
+ if (propName !== null) {
46365
+ if (isRuntimeClassInstance(v)) answer = v.fields.has(propName);
46366
+ else if (isRuntimeClassInstanceArray(v))
46367
+ answer = v.elements.length > 0 && v.elements[0].fields.has(propName);
46368
+ }
46369
+ const shape = getShape(v);
46370
+ const n = shape.reduce((a, b) => a * b, 1);
46371
+ if (n === 1) return RTV.logical(answer);
46372
+ const data = allocFloat64Array(n);
46373
+ if (answer) data.fill(1);
46374
+ return new RuntimeTensor(data, shape, void 0, true);
46375
+ }
46376
+ }
46377
+ ]
46378
+ });
46379
+ defineBuiltin({
46380
+ name: "addprop",
46381
+ help: {
46382
+ signatures: ["p = addprop(obj, PropertyName)"],
46383
+ 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."
46384
+ },
46385
+ cases: [
46386
+ {
46387
+ match: (argTypes) => {
46388
+ if (argTypes.length !== 2) return null;
46389
+ return [{ kind: "unknown" }];
46390
+ },
46391
+ apply: (args) => {
46392
+ const obj = args[0];
46393
+ if (!isRuntimeClassInstance(obj))
46394
+ throw new RuntimeError(
46395
+ "addprop: first argument must be a dynamicprops object"
46396
+ );
46397
+ const name = toString(args[1]);
46398
+ if (!obj.fields.has(name)) {
46399
+ const empty = RTV.tensor(allocFloat64Array(0), [0, 0]);
46400
+ incref(empty);
46401
+ obj.fields.set(name, empty);
46402
+ }
46403
+ return RTV.classInstance(
46404
+ "meta.DynamicProperty",
46405
+ ["Name"],
46406
+ true,
46407
+ /* @__PURE__ */ new Map([["Name", RTV.char(name)]])
46408
+ );
46409
+ }
46410
+ }
46411
+ ]
46412
+ });
46058
46413
  defineBuiltin({
46059
46414
  name: "isscalar",
46060
46415
  cases: [
@@ -46252,6 +46607,30 @@ function mkChar(value) {
46252
46607
  defineBuiltin({
46253
46608
  name: "class",
46254
46609
  cases: [
46610
+ // Old-style (pre-classdef) constructor form: class(structData, 'ClassName')
46611
+ // builds a value-type instance whose fields are the struct's fields. The
46612
+ // optional class(s,'Name',parent,...) inheritance form is not supported
46613
+ // (returns null → "unsupported argument types").
46614
+ {
46615
+ match: (argTypes) => {
46616
+ if (argTypes.length !== 2) return null;
46617
+ if (argTypes[0].kind !== "struct") return null;
46618
+ const k = argTypes[1].kind;
46619
+ if (k !== "char" && k !== "string") return null;
46620
+ return [{ kind: "unknown" }];
46621
+ },
46622
+ apply: (args) => {
46623
+ const s = args[0];
46624
+ if (!isRuntimeStruct(s))
46625
+ throw new RuntimeError(
46626
+ "class: first argument must be a scalar struct"
46627
+ );
46628
+ const nameVal = args[1];
46629
+ const className = isRuntimeChar(nameVal) ? nameVal.value : isRuntimeString(nameVal) ? nameVal : String(nameVal);
46630
+ const fieldNames = [...s.fields.keys()];
46631
+ return RTV.classInstance(className, fieldNames, false, s.fields);
46632
+ }
46633
+ },
46255
46634
  {
46256
46635
  match: (argTypes) => {
46257
46636
  if (argTypes.length !== 1) return null;
@@ -46289,6 +46668,26 @@ defineBuiltin({
46289
46668
  }
46290
46669
  ]
46291
46670
  });
46671
+ for (const name of ["superiorto", "inferiorto"]) {
46672
+ defineBuiltin({
46673
+ name,
46674
+ help: {
46675
+ signatures: [`${name}('Class1', 'Class2', ...)`],
46676
+ 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."
46677
+ },
46678
+ cases: [
46679
+ {
46680
+ match: (argTypes) => {
46681
+ for (const t of argTypes) {
46682
+ if (t.kind !== "char" && t.kind !== "string") return null;
46683
+ }
46684
+ return [];
46685
+ },
46686
+ apply: () => void 0
46687
+ }
46688
+ ]
46689
+ });
46690
+ }
46292
46691
  function fieldnamesApply(args) {
46293
46692
  if (args.length !== 1)
46294
46693
  throw new RuntimeError("fieldnames requires 1 argument");
@@ -46856,7 +47255,6 @@ function anyAllApply(name, mode) {
46856
47255
  if (isRuntimeComplexNumber(v)) return RTV.logical(v.re !== 0 || v.im !== 0);
46857
47256
  if (isRuntimeTensor(v)) {
46858
47257
  if (args.length === 1) {
46859
- if (v.data.length === 0) return RTV.logical(mode === "all");
46860
47258
  const d = firstReduceDim(v.shape);
46861
47259
  if (d === 0) return RTV.logical(scanLogical(v.data, v.imag, mode));
46862
47260
  return logicalAlongDim(v, d, mode);
@@ -47126,7 +47524,12 @@ function applyTextFn(v, fn) {
47126
47524
  }
47127
47525
  return RTV.cell(out, [...v.shape]);
47128
47526
  }
47129
- if (isRuntimeChar(v)) return RTV.char(fn(v.value));
47527
+ if (isRuntimeChar(v)) {
47528
+ if (v.shape && (v.shape[0] ?? 1) > 1) {
47529
+ return charRowsToMatrix(valueToCharRows(v).map(fn));
47530
+ }
47531
+ return RTV.char(fn(v.value));
47532
+ }
47130
47533
  if (isRuntimeString(v)) return RTV.string(fn(toString(v)));
47131
47534
  return v;
47132
47535
  }
@@ -47278,7 +47681,7 @@ registerIBuiltin({
47278
47681
  const str = toString(args[0]);
47279
47682
  const pat = toString(args[1]);
47280
47683
  const rep = toString(args[2]);
47281
- let flags = "g";
47684
+ let flags = "gs";
47282
47685
  for (let i = 3; i < args.length; i++) {
47283
47686
  const opt = toString(args[i]).toLowerCase();
47284
47687
  if (opt === "ignorecase") flags += "i";
@@ -47642,6 +48045,38 @@ registerIBuiltin({
47642
48045
  };
47643
48046
  }
47644
48047
  });
48048
+ registerIBuiltin({
48049
+ name: "isspace",
48050
+ help: {
48051
+ signatures: ["TF = isspace(str)"],
48052
+ description: "Return a logical array the same size as str, true where the character is whitespace. Numeric input is treated as Unicode code points."
48053
+ },
48054
+ resolve: (argTypes) => {
48055
+ if (argTypes.length !== 1) return null;
48056
+ const k = argTypes[0].kind;
48057
+ if (k !== "char" && k !== "string" && k !== "number" && k !== "boolean" && k !== "tensor") {
48058
+ return null;
48059
+ }
48060
+ const pred = (cp) => RE_WSPACE.test(String.fromCodePoint(cp));
48061
+ return {
48062
+ outputTypes: [{ kind: "tensor", isComplex: false, isLogical: true }],
48063
+ apply: (args) => {
48064
+ const v = args[0];
48065
+ if (isRuntimeChar(v)) {
48066
+ return logicalRowFromString(
48067
+ v.value,
48068
+ pred,
48069
+ v.shape ? [...v.shape] : void 0
48070
+ );
48071
+ }
48072
+ if (isRuntimeString(v)) return logicalRowFromString(toString(v), pred);
48073
+ if (isRuntimeTensor(v))
48074
+ return logicalFromNumericTensor(v.data, v.shape, pred);
48075
+ return logicalFromNumericTensor([toNumber(v)], [1, 1], pred);
48076
+ }
48077
+ };
48078
+ }
48079
+ });
47645
48080
  registerIBuiltin({
47646
48081
  name: "contains",
47647
48082
  resolve: (argTypes) => {
@@ -48089,12 +48524,102 @@ registerIBuiltin({
48089
48524
  };
48090
48525
  }
48091
48526
  });
48527
+ function collectRows(v) {
48528
+ if (isRuntimeCell(v)) {
48529
+ const rows = [];
48530
+ for (const el of v.data) rows.push(...valueToCharRows(el));
48531
+ return rows;
48532
+ }
48533
+ return valueToCharRows(v);
48534
+ }
48535
+ registerIBuiltin({
48536
+ name: "strmatch",
48537
+ help: {
48538
+ signatures: [
48539
+ "x = strmatch(str, strarray)",
48540
+ "x = strmatch(str, strarray, 'exact')"
48541
+ ],
48542
+ 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."
48543
+ },
48544
+ resolve: (argTypes) => {
48545
+ if (argTypes.length < 2 || argTypes.length > 3) return null;
48546
+ if (!isTextType(argTypes[0])) return null;
48547
+ const sa = argTypes[1];
48548
+ if (sa.kind !== "char" && sa.kind !== "string" && sa.kind !== "cell")
48549
+ return null;
48550
+ return {
48551
+ outputTypes: [{ kind: "tensor", isComplex: false }],
48552
+ apply: (args) => {
48553
+ const str = toString(args[0]);
48554
+ let rows = collectRows(args[1]);
48555
+ const n = rows.length === 0 ? 0 : Math.max(...rows.map((r) => r.length));
48556
+ rows = rows.map((r) => r.padEnd(n, " "));
48557
+ const exactMatch = args.length === 3;
48558
+ let s = str;
48559
+ let len = s.length;
48560
+ if (len > n) {
48561
+ return RTV.tensor(allocFloat64Array(0), [0, 1]);
48562
+ }
48563
+ if (exactMatch && len < n) {
48564
+ const useNull = rows.some((r) => r.charCodeAt(n - 1) === 0);
48565
+ s = s.padEnd(n, useNull ? "\0" : " ");
48566
+ len = n;
48567
+ }
48568
+ const matches2 = [];
48569
+ for (let r = 0; r < rows.length; r++) {
48570
+ let ok = true;
48571
+ for (let i = 0; i < len; i++) {
48572
+ if (rows[r][i] !== s[i]) {
48573
+ ok = false;
48574
+ break;
48575
+ }
48576
+ }
48577
+ if (ok) matches2.push(r + 1);
48578
+ }
48579
+ return RTV.tensor(allocFloat64Array(matches2), [matches2.length, 1]);
48580
+ }
48581
+ };
48582
+ }
48583
+ });
48584
+ function valueToCharRows(v) {
48585
+ if (isRuntimeChar(v)) {
48586
+ const cols = v.shape ? v.shape[1] ?? v.value.length : v.value.length;
48587
+ const numRows = v.shape ? v.shape[0] ?? 1 : 1;
48588
+ if (numRows <= 1) return [v.value];
48589
+ const out = [];
48590
+ for (let r = 0; r < numRows; r++)
48591
+ out.push(v.value.slice(r * cols, (r + 1) * cols));
48592
+ return out;
48593
+ }
48594
+ if (isRuntimeString(v)) return [v];
48595
+ if (isRuntimeNumber(v)) return [String.fromCharCode(Math.round(v))];
48596
+ if (isRuntimeTensor(v)) {
48597
+ const rows = v.shape.length >= 2 ? v.shape[0] ?? 1 : 1;
48598
+ const cols = v.shape.length >= 2 ? v.shape[1] ?? 0 : v.data.length;
48599
+ const out = [];
48600
+ for (let r = 0; r < rows; r++) {
48601
+ let s = "";
48602
+ for (let c = 0; c < cols; c++)
48603
+ s += String.fromCharCode(Math.round(v.data[c * rows + r]));
48604
+ out.push(s);
48605
+ }
48606
+ return out;
48607
+ }
48608
+ throw new RuntimeError("char: unsupported cell element type");
48609
+ }
48610
+ function charRowsToMatrix(rows) {
48611
+ if (rows.length === 0) return RTV.char("");
48612
+ const width = Math.max(...rows.map((r) => r.length));
48613
+ const padded = rows.map((r) => r.padEnd(width, " "));
48614
+ if (rows.length === 1) return RTV.char(padded[0]);
48615
+ return new RuntimeChar(padded.join(""), [rows.length, width]);
48616
+ }
48092
48617
  registerIBuiltin({
48093
48618
  name: "char",
48094
48619
  resolve: (argTypes) => {
48095
48620
  if (argTypes.length !== 1) return null;
48096
48621
  const a = argTypes[0];
48097
- if (a.kind !== "char" && a.kind !== "string" && a.kind !== "number" && a.kind !== "tensor")
48622
+ if (a.kind !== "char" && a.kind !== "string" && a.kind !== "number" && a.kind !== "tensor" && a.kind !== "cell")
48098
48623
  return null;
48099
48624
  return {
48100
48625
  outputTypes: [{ kind: "char" }],
@@ -48111,6 +48636,11 @@ registerIBuiltin({
48111
48636
  }
48112
48637
  return RTV.char(chars.join(""));
48113
48638
  }
48639
+ if (isRuntimeCell(v)) {
48640
+ const rows = [];
48641
+ for (const el of v.data) rows.push(...valueToCharRows(el));
48642
+ return charRowsToMatrix(rows);
48643
+ }
48114
48644
  throw new RuntimeError("char: unsupported arguments");
48115
48645
  }
48116
48646
  };
@@ -48178,6 +48708,8 @@ registerIBuiltin({
48178
48708
  if (!isTextType(argTypes[0]) || !isTextType(argTypes[1])) return null;
48179
48709
  const outputTypes = [{ kind: "number" }];
48180
48710
  if (nargout >= 2) outputTypes.push({ kind: "number" });
48711
+ if (nargout >= 3) outputTypes.push({ kind: "char" });
48712
+ if (nargout >= 4) outputTypes.push({ kind: "number" });
48181
48713
  return {
48182
48714
  outputTypes,
48183
48715
  apply: (args, nout) => {
@@ -48187,6 +48719,7 @@ registerIBuiltin({
48187
48719
  const results = [];
48188
48720
  let strPos = 0;
48189
48721
  let fmtPos = 0;
48722
+ let matchFailure = false;
48190
48723
  while (fmtPos < fmt.length && strPos < str.length && results.length < maxCount) {
48191
48724
  if (fmt[fmtPos] === "%") {
48192
48725
  fmtPos++;
@@ -48198,22 +48731,34 @@ registerIBuiltin({
48198
48731
  }
48199
48732
  if (spec === "d" || spec === "i") {
48200
48733
  const m = str.slice(strPos).match(/^[+-]?\d+/);
48201
- if (!m) break;
48734
+ if (!m) {
48735
+ matchFailure = true;
48736
+ break;
48737
+ }
48202
48738
  results.push(parseInt(m[0], 10));
48203
48739
  strPos += m[0].length;
48204
48740
  } else if (spec === "f" || spec === "e" || spec === "g") {
48205
48741
  const m = str.slice(strPos).match(/^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?/);
48206
- if (!m) break;
48742
+ if (!m) {
48743
+ matchFailure = true;
48744
+ break;
48745
+ }
48207
48746
  results.push(parseFloat(m[0]));
48208
48747
  strPos += m[0].length;
48209
48748
  } else if (spec === "x") {
48210
48749
  const m = str.slice(strPos).match(/^[+-]?[0-9a-fA-F]+/);
48211
- if (!m) break;
48750
+ if (!m) {
48751
+ matchFailure = true;
48752
+ break;
48753
+ }
48212
48754
  results.push(parseInt(m[0], 16));
48213
48755
  strPos += m[0].length;
48214
48756
  } else if (spec === "o") {
48215
48757
  const m = str.slice(strPos).match(/^[+-]?[0-7]+/);
48216
- if (!m) break;
48758
+ if (!m) {
48759
+ matchFailure = true;
48760
+ break;
48761
+ }
48217
48762
  results.push(parseInt(m[0], 8));
48218
48763
  strPos += m[0].length;
48219
48764
  } else if (spec === "c") {
@@ -48221,7 +48766,10 @@ registerIBuiltin({
48221
48766
  strPos++;
48222
48767
  } else if (spec === "s") {
48223
48768
  const m = str.slice(strPos).match(/^\S+/);
48224
- if (!m) break;
48769
+ if (!m) {
48770
+ matchFailure = true;
48771
+ break;
48772
+ }
48225
48773
  for (let ci = 0; ci < m[0].length && results.length < maxCount; ci++) {
48226
48774
  results.push(m[0].charCodeAt(ci));
48227
48775
  }
@@ -48231,7 +48779,10 @@ registerIBuiltin({
48231
48779
  fmtPos++;
48232
48780
  while (strPos < str.length && /\s/.test(str[strPos])) strPos++;
48233
48781
  } else {
48234
- if (str[strPos] !== fmt[fmtPos]) break;
48782
+ if (str[strPos] !== fmt[fmtPos]) {
48783
+ matchFailure = true;
48784
+ break;
48785
+ }
48235
48786
  strPos++;
48236
48787
  fmtPos++;
48237
48788
  }
@@ -48241,7 +48792,16 @@ registerIBuiltin({
48241
48792
  }
48242
48793
  const vals = results.length === 1 ? RTV.num(results[0]) : RTV.tensor(allocFloat64Array(results), [results.length, 1]);
48243
48794
  if (nout >= 2) {
48244
- return [vals, RTV.num(results.length)];
48795
+ const out = [vals, RTV.num(results.length)];
48796
+ if (nout >= 3) {
48797
+ out.push(
48798
+ RTV.char(matchFailure ? "Matching failure in format." : "")
48799
+ );
48800
+ }
48801
+ if (nout >= 4) {
48802
+ out.push(RTV.num(strPos + 1));
48803
+ }
48804
+ return out;
48245
48805
  }
48246
48806
  return vals;
48247
48807
  }
@@ -49652,8 +50212,34 @@ function normImplTensor(v, args) {
49652
50212
  return RTV.num(maxRowSum);
49653
50213
  }
49654
50214
  if (p2 === 2) {
50215
+ let anyNaN = false;
50216
+ let anyInf = false;
50217
+ for (let i = 0; i < v.data.length; i++) {
50218
+ const re = v.data[i];
50219
+ const im = imag2 ? imag2[i] : 0;
50220
+ if (Number.isNaN(re) || Number.isNaN(im)) anyNaN = true;
50221
+ else if (!isFinite(re) || !isFinite(im)) anyInf = true;
50222
+ }
50223
+ if (anyNaN) return RTV.num(NaN);
50224
+ if (anyInf) return RTV.num(Infinity);
49655
50225
  const bridge = getEffectiveBridge("norm", "svd");
49656
50226
  if (bridge && bridge.svd) {
50227
+ if (imag2) {
50228
+ const R = allocFloat64Array(4 * rows * cols);
50229
+ const tm = 2 * rows;
50230
+ for (let j = 0; j < cols; j++) {
50231
+ for (let i = 0; i < rows; i++) {
50232
+ const a = v.data[j * rows + i];
50233
+ const b = imag2[j * rows + i];
50234
+ R[j * tm + i] = a;
50235
+ R[j * tm + (rows + i)] = b;
50236
+ R[(cols + j) * tm + i] = -b;
50237
+ R[(cols + j) * tm + (rows + i)] = a;
50238
+ }
50239
+ }
50240
+ const result2 = bridge.svd(R, tm, 2 * cols, false, false);
50241
+ return RTV.num(result2.S[0]);
50242
+ }
49657
50243
  const f64 = v.data instanceof Float64Array ? v.data : allocFloat64Array(v.data);
49658
50244
  const result = bridge.svd(f64, rows, cols, false, false);
49659
50245
  return RTV.num(result.S[0]);
@@ -50413,6 +50999,100 @@ function svdApply(args, nargout) {
50413
50999
  [k, 1]
50414
51000
  );
50415
51001
  }
51002
+ function nullApply(args) {
51003
+ let A = args[0];
51004
+ if (isRuntimeNumber(A) || isRuntimeComplexNumber(A)) {
51005
+ A = isRuntimeNumber(A) ? RTV.tensor(allocFloat64Array([A]), [1, 1]) : RTV.tensor(
51006
+ allocFloat64Array([A.re]),
51007
+ [1, 1],
51008
+ allocFloat64Array([A.im])
51009
+ );
51010
+ }
51011
+ if (!isRuntimeTensor(A))
51012
+ throw new RuntimeError("null: argument must be a numeric matrix");
51013
+ const [m, n] = tensorSize2D(A);
51014
+ const [, Sdiag, V] = svdApply([A], 3);
51015
+ const k = Math.min(m, n);
51016
+ const s = [];
51017
+ for (let i = 0; i < k; i++) s.push(Sdiag.data[colMajorIndex(i, i, m)]);
51018
+ const maxS = s.length > 0 ? Math.max(...s) : 0;
51019
+ const tol = args.length > 1 && args[1] !== void 0 ? toNumber(args[1]) : Math.max(m, n) * epsOf(maxS);
51020
+ let r = 0;
51021
+ for (const sv of s) if (sv > tol) r++;
51022
+ const cols = n - r;
51023
+ const out = allocFloat64Array(n * cols);
51024
+ const outImag = V.imag ? allocFloat64Array(n * cols) : void 0;
51025
+ for (let c = 0; c < cols; c++) {
51026
+ for (let i = 0; i < n; i++) {
51027
+ const src = colMajorIndex(i, r + c, n);
51028
+ out[colMajorIndex(i, c, n)] = V.data[src];
51029
+ if (outImag) outImag[colMajorIndex(i, c, n)] = V.imag[src];
51030
+ }
51031
+ }
51032
+ return RTV.tensor(out, [n, cols], outImag);
51033
+ }
51034
+ registerIBuiltin({
51035
+ name: "null",
51036
+ resolve: (argTypes, nargout) => {
51037
+ if (nargout > 1 || argTypes.length < 1 || argTypes.length > 2) return null;
51038
+ if (!isNumericJitType(argTypes[0])) return null;
51039
+ const isComplex = argTypes[0].kind === "tensor" && argTypes[0].isComplex;
51040
+ return {
51041
+ outputTypes: [tensorType(isComplex || void 0)],
51042
+ apply: (args) => nullApply(args)
51043
+ };
51044
+ }
51045
+ });
51046
+ function computeBandwidth(A) {
51047
+ let lower = 0;
51048
+ let upper = 0;
51049
+ const note = (i, j) => {
51050
+ if (i > j) lower = Math.max(lower, i - j);
51051
+ else if (j > i) upper = Math.max(upper, j - i);
51052
+ };
51053
+ if (isRuntimeNumber(A) || isRuntimeComplexNumber(A)) return [0, 0];
51054
+ if (isRuntimeSparseMatrix(A)) {
51055
+ for (let j = 0; j < A.n; j++) {
51056
+ for (let k = A.jc[j]; k < A.jc[j + 1]; k++) {
51057
+ if (A.pr[k] === 0 && (!A.pi || A.pi[k] === 0)) continue;
51058
+ note(A.ir[k], j);
51059
+ }
51060
+ }
51061
+ return [lower, upper];
51062
+ }
51063
+ if (isRuntimeTensor(A)) {
51064
+ const [m, n] = tensorSize2D(A);
51065
+ for (let j = 0; j < n; j++) {
51066
+ for (let i = 0; i < m; i++) {
51067
+ const idx = colMajorIndex(i, j, m);
51068
+ if (A.data[idx] === 0 && (!A.imag || A.imag[idx] === 0)) continue;
51069
+ note(i, j);
51070
+ }
51071
+ }
51072
+ return [lower, upper];
51073
+ }
51074
+ throw new RuntimeError("bandwidth: first argument must be a numeric matrix");
51075
+ }
51076
+ function bandwidthApply(args, nargout) {
51077
+ const [lower, upper] = computeBandwidth(args[0]);
51078
+ if (args.length >= 2 && args[1] !== void 0) {
51079
+ const type = parseStringArgLower(args[1]);
51080
+ if (type === "lower") return RTV.num(lower);
51081
+ if (type === "upper") return RTV.num(upper);
51082
+ throw new RuntimeError("bandwidth: TYPE must be 'lower' or 'upper'");
51083
+ }
51084
+ if (nargout >= 2) return [RTV.num(lower), RTV.num(upper)];
51085
+ return RTV.num(lower);
51086
+ }
51087
+ registerIBuiltin({
51088
+ name: "bandwidth",
51089
+ resolve: (argTypes, nargout) => {
51090
+ if (nargout > 2 || argTypes.length < 1 || argTypes.length > 2) return null;
51091
+ if (!isNumericJitType(argTypes[0])) return null;
51092
+ const outs = nargout >= 2 ? [NUM, NUM] : [NUM];
51093
+ return { outputTypes: outs, apply: (args, n) => bandwidthApply(args, n) };
51094
+ }
51095
+ });
50416
51096
  function computeATA(A_data, m, n) {
50417
51097
  const result = allocFloat64Array(n * n);
50418
51098
  for (let j = 0; j < n; j++)
@@ -52283,6 +52963,33 @@ function validateSizeArg(x) {
52283
52963
  }
52284
52964
  return x < 0 ? 0 : x;
52285
52965
  }
52966
+ function parseReshapeDims(args, total) {
52967
+ let rawDims;
52968
+ if (args.length === 2 && isRuntimeTensor(args[1]) && args[1].data.length > 1) {
52969
+ rawDims = Array.from(args[1].data).map((x) => validateSizeArg(x));
52970
+ } else {
52971
+ rawDims = args.slice(1).map((a) => {
52972
+ if (isRuntimeTensor(a) && a.data.length === 0) return null;
52973
+ return validateSizeArg(toNumber(a));
52974
+ });
52975
+ }
52976
+ const autoCount = rawDims.filter((d) => d === null).length;
52977
+ if (autoCount > 1)
52978
+ throw new RuntimeError("reshape: only one dimension size can be []");
52979
+ let shape;
52980
+ if (autoCount === 1) {
52981
+ const known = rawDims.filter((d) => d !== null);
52982
+ const knownProduct = known.reduce((a, b) => a * b, 1);
52983
+ if (knownProduct === 0 || total % knownProduct !== 0)
52984
+ throw new RuntimeError("reshape: number of elements must not change");
52985
+ shape = rawDims.map((d) => d === null ? total / knownProduct : d);
52986
+ } else {
52987
+ shape = rawDims;
52988
+ }
52989
+ if (numel(shape) !== total)
52990
+ throw new RuntimeError("reshape: number of elements must not change");
52991
+ return shape;
52992
+ }
52286
52993
  defineBuiltin({
52287
52994
  name: "reshape",
52288
52995
  cases: [
@@ -52294,31 +53001,31 @@ defineBuiltin({
52294
53001
  const v = args[0];
52295
53002
  if (isRuntimeSparseMatrix(v)) {
52296
53003
  const totalEl = v.m * v.n;
52297
- let rawDims2;
53004
+ let rawDims;
52298
53005
  if (args.length === 2 && isRuntimeTensor(args[1]) && args[1].data.length > 1) {
52299
- rawDims2 = Array.from(args[1].data).map((x) => Math.round(x));
53006
+ rawDims = Array.from(args[1].data).map((x) => Math.round(x));
52300
53007
  } else {
52301
- rawDims2 = args.slice(1).map((a) => {
53008
+ rawDims = args.slice(1).map((a) => {
52302
53009
  if (isRuntimeTensor(a) && a.data.length === 0) return null;
52303
53010
  return Math.round(toNumber(a));
52304
53011
  });
52305
53012
  }
52306
- const autoCount2 = rawDims2.filter((d) => d === null).length;
52307
- if (autoCount2 > 1)
53013
+ const autoCount = rawDims.filter((d) => d === null).length;
53014
+ if (autoCount > 1)
52308
53015
  throw new RuntimeError(
52309
53016
  "reshape: only one dimension size can be []"
52310
53017
  );
52311
53018
  let shape2;
52312
- if (autoCount2 === 1) {
52313
- const known = rawDims2.filter((d) => d !== null);
53019
+ if (autoCount === 1) {
53020
+ const known = rawDims.filter((d) => d !== null);
52314
53021
  const knownProduct = known.reduce((a, b) => a * b, 1);
52315
53022
  if (totalEl % knownProduct !== 0)
52316
53023
  throw new RuntimeError(
52317
53024
  "reshape: number of elements must not change"
52318
53025
  );
52319
- shape2 = rawDims2.map((d) => d === null ? totalEl / knownProduct : d);
53026
+ shape2 = rawDims.map((d) => d === null ? totalEl / knownProduct : d);
52320
53027
  } else {
52321
- shape2 = rawDims2;
53028
+ shape2 = rawDims;
52322
53029
  }
52323
53030
  if (shape2.length !== 2)
52324
53031
  throw new RuntimeError("reshape: sparse matrices must be 2-D");
@@ -52356,40 +53063,32 @@ defineBuiltin({
52356
53063
  jc[newN] = ti;
52357
53064
  return RTV.sparseMatrix(newM, newN, ir, jc, pr, pi2);
52358
53065
  }
53066
+ if (isRuntimeChar(v)) {
53067
+ const srcRows = v.shape ? v.shape[0] : 1;
53068
+ const srcCols = v.shape ? v.shape[1] ?? 0 : v.value.length;
53069
+ const total = v.value.length;
53070
+ const reqShape = parseReshapeDims(args, total);
53071
+ const s = [...reqShape];
53072
+ while (s.length > 2 && s[s.length - 1] === 1) s.pop();
53073
+ if (s.length > 2)
53074
+ throw new RuntimeError("reshape: char arrays must be 2-D");
53075
+ const nr = s[0];
53076
+ const nc = s.length >= 2 ? s[1] : 1;
53077
+ const linear = new Array(total);
53078
+ for (let j = 0; j < srcCols; j++)
53079
+ for (let i = 0; i < srcRows; i++)
53080
+ linear[j * srcRows + i] = v.value[i * srcCols + j];
53081
+ const chars = new Array(total);
53082
+ for (let j2 = 0; j2 < nc; j2++)
53083
+ for (let i2 = 0; i2 < nr; i2++)
53084
+ chars[i2 * nc + j2] = linear[j2 * nr + i2];
53085
+ return new RuntimeChar(chars.join(""), [nr, nc]);
53086
+ }
52359
53087
  if (!isRuntimeTensor(v) && !isRuntimeNumber(v) && !isRuntimeComplexNumber(v))
52360
53088
  throw new RuntimeError("reshape: first argument must be numeric");
52361
53089
  const data = isRuntimeTensor(v) ? v.data : isRuntimeComplexNumber(v) ? allocFloat64Array([v.re]) : allocFloat64Array([v]);
52362
53090
  const imag2 = isRuntimeTensor(v) ? v.imag : isRuntimeComplexNumber(v) ? allocFloat64Array([v.im]) : void 0;
52363
- let rawDims;
52364
- if (args.length === 2 && isRuntimeTensor(args[1]) && args[1].data.length > 1) {
52365
- rawDims = Array.from(args[1].data).map((x) => validateSizeArg(x));
52366
- } else {
52367
- rawDims = args.slice(1).map((a) => {
52368
- if (isRuntimeTensor(a) && a.data.length === 0) return null;
52369
- return validateSizeArg(toNumber(a));
52370
- });
52371
- }
52372
- const autoCount = rawDims.filter((d) => d === null).length;
52373
- if (autoCount > 1)
52374
- throw new RuntimeError("reshape: only one dimension size can be []");
52375
- let shape;
52376
- if (autoCount === 1) {
52377
- const known = rawDims.filter((d) => d !== null);
52378
- const knownProduct = known.reduce((a, b) => a * b, 1);
52379
- if (data.length % knownProduct !== 0)
52380
- throw new RuntimeError(
52381
- "reshape: number of elements must not change"
52382
- );
52383
- shape = rawDims.map(
52384
- (d) => d === null ? data.length / knownProduct : d
52385
- );
52386
- } else {
52387
- shape = rawDims;
52388
- }
52389
- const n = numel(shape);
52390
- if (n !== data.length) {
52391
- throw new RuntimeError("reshape: number of elements must not change");
52392
- }
53091
+ const shape = parseReshapeDims(args, data.length);
52393
53092
  if (isRuntimeTensor(v)) {
52394
53093
  const dataCopy = allocFloat64Array(data);
52395
53094
  const imagCopy = imag2 ? allocFloat64Array(imag2) : void 0;
@@ -52801,6 +53500,35 @@ defineBuiltin({
52801
53500
  }
52802
53501
  ]
52803
53502
  });
53503
+ function repmatObjects(v, repArgs) {
53504
+ const srcElements = isRuntimeClassInstanceArray(v) ? v.elements : [v];
53505
+ const [rows, cols] = isRuntimeClassInstanceArray(v) ? v.shape : [1, 1];
53506
+ const className = v.className;
53507
+ let reps;
53508
+ if (repArgs.length === 1) {
53509
+ const arg1 = repArgs[0];
53510
+ if (isRuntimeTensor(arg1)) {
53511
+ reps = Array.from(arg1.data).map((x) => validateSizeArg(x));
53512
+ } else {
53513
+ const n = validateSizeArg(toNumber(arg1));
53514
+ reps = [n, n];
53515
+ }
53516
+ } else {
53517
+ reps = repArgs.map((a) => validateSizeArg(toNumber(a)));
53518
+ }
53519
+ const rRep = reps[0] ?? 1;
53520
+ const cRep = reps.length >= 2 ? reps[1] : reps[0] ?? 1;
53521
+ const newRows = rows * rRep;
53522
+ const newCols = cols * cRep;
53523
+ const out = new Array(Math.max(0, newRows * newCols));
53524
+ for (let J = 0; J < newCols; J++) {
53525
+ for (let I = 0; I < newRows; I++) {
53526
+ const src = srcElements[I % rows + J % cols * rows];
53527
+ out[I + J * newRows] = copyClassInstance(src);
53528
+ }
53529
+ }
53530
+ return out.length === 1 ? out[0] : RTV.classInstanceArray(className, out, [newRows, newCols]);
53531
+ }
52804
53532
  defineBuiltin({
52805
53533
  name: "repmat",
52806
53534
  cases: [
@@ -52825,6 +53553,8 @@ defineBuiltin({
52825
53553
  if (args.length < 2)
52826
53554
  throw new RuntimeError("repmat requires at least 2 arguments");
52827
53555
  let v = args[0];
53556
+ if (isRuntimeClassInstance(v) || isRuntimeClassInstanceArray(v))
53557
+ return repmatObjects(v, args.slice(1));
52828
53558
  if (isRuntimeSparseMatrix(v)) v = sparseToDense(v);
52829
53559
  let reps;
52830
53560
  if (args.length === 2) {
@@ -52989,6 +53719,62 @@ defineBuiltin({
52989
53719
  }
52990
53720
  ]
52991
53721
  });
53722
+ function copyClassInstance(inst) {
53723
+ if (inst.isHandleClass) return inst;
53724
+ return new RuntimeClassInstance(
53725
+ inst.className,
53726
+ new Map(inst.fields),
53727
+ inst.isHandleClass,
53728
+ inst._builtinData
53729
+ );
53730
+ }
53731
+ function repelemObjects(v, repArgs) {
53732
+ const srcElements = isRuntimeClassInstanceArray(v) ? v.elements : [v];
53733
+ const [rows, cols] = isRuntimeClassInstanceArray(v) ? v.shape : [1, 1];
53734
+ const className = v.className;
53735
+ const wrap = (elements, shape) => elements.length === 1 ? elements[0] : RTV.classInstanceArray(className, elements, shape);
53736
+ if (repArgs.length === 1) {
53737
+ const repArg = repArgs[0];
53738
+ if (isRuntimeTensor(repArg) && repArg.data.length > 1) {
53739
+ const counts = repArg.data;
53740
+ if (counts.length !== srcElements.length)
53741
+ throw new RuntimeError(
53742
+ `repelem: counts vector length (${counts.length}) must match the number of elements (${srcElements.length})`
53743
+ );
53744
+ const out3 = [];
53745
+ for (let i = 0; i < srcElements.length; i++) {
53746
+ const c = Math.max(0, Math.round(counts[i]));
53747
+ for (let j = 0; j < c; j++) out3.push(copyClassInstance(srcElements[i]));
53748
+ }
53749
+ const isCol2 = cols === 1 && rows !== 1;
53750
+ return wrap(out3, isCol2 ? [out3.length, 1] : [1, out3.length]);
53751
+ }
53752
+ const n = Math.max(0, Math.round(toNumber(repArg)));
53753
+ const out2 = [];
53754
+ for (const el of srcElements)
53755
+ for (let j = 0; j < n; j++) out2.push(copyClassInstance(el));
53756
+ const isCol = cols === 1 && rows !== 1;
53757
+ return wrap(out2, isCol ? [out2.length, 1] : [1, out2.length]);
53758
+ }
53759
+ const rRep = Math.max(0, Math.round(toNumber(repArgs[0])));
53760
+ const cRep = Math.max(0, Math.round(toNumber(repArgs[1])));
53761
+ const newRows = rows * rRep;
53762
+ const newCols = cols * cRep;
53763
+ const out = new Array(newRows * newCols);
53764
+ for (let c = 0; c < cols; c++) {
53765
+ for (let r = 0; r < rows; r++) {
53766
+ const src = srcElements[c * rows + r];
53767
+ for (let dc = 0; dc < cRep; dc++) {
53768
+ for (let dr = 0; dr < rRep; dr++) {
53769
+ const dstRow = r * rRep + dr;
53770
+ const dstCol = c * cRep + dc;
53771
+ out[dstCol * newRows + dstRow] = copyClassInstance(src);
53772
+ }
53773
+ }
53774
+ }
53775
+ }
53776
+ return wrap(out, [newRows, newCols]);
53777
+ }
52992
53778
  defineBuiltin({
52993
53779
  name: "repelem",
52994
53780
  cases: [
@@ -52998,6 +53784,8 @@ defineBuiltin({
52998
53784
  if (args.length < 2)
52999
53785
  throw new RuntimeError("repelem requires at least 2 arguments");
53000
53786
  const v = args[0];
53787
+ if (isRuntimeClassInstance(v) || isRuntimeClassInstanceArray(v))
53788
+ return repelemObjects(v, args.slice(1));
53001
53789
  if (args.length === 2) {
53002
53790
  const repArg = args[1];
53003
53791
  if (isRuntimeTensor(repArg) && repArg.data.length > 1) {
@@ -53120,7 +53908,15 @@ defineBuiltin({
53120
53908
  }
53121
53909
  return RTV.tensor(dataCopy, newShape, imagCopy);
53122
53910
  }
53123
- throw new RuntimeError("squeeze: argument must be numeric");
53911
+ if (isRuntimeCell(v)) {
53912
+ const shape = [...v.shape];
53913
+ while (shape.length > 2 && shape[shape.length - 1] === 1) shape.pop();
53914
+ if (shape.length <= 2) return RTV.cell(v.data, shape);
53915
+ const newShape = shape.filter((d) => d !== 1);
53916
+ while (newShape.length < 2) newShape.push(1);
53917
+ return RTV.cell(v.data, newShape);
53918
+ }
53919
+ return v;
53124
53920
  }
53125
53921
  }
53126
53922
  ]
@@ -53790,6 +54586,8 @@ defineBuiltin({
53790
54586
  ]
53791
54587
  });
53792
54588
  function bitwiseOp(a, b, op, name) {
54589
+ if (typeof a === "boolean") a = a ? 1 : 0;
54590
+ if (typeof b === "boolean") b = b ? 1 : 0;
53793
54591
  if (isRuntimeNumber(a) && isRuntimeNumber(b)) {
53794
54592
  return RTV.num(op(Math.round(a), Math.round(b)));
53795
54593
  }
@@ -56057,6 +56855,56 @@ defineBuiltin({
56057
56855
  }
56058
56856
  ]
56059
56857
  });
56858
+ function froundArray(src) {
56859
+ const out = allocFloat64Array(src.length);
56860
+ for (let i = 0; i < src.length; i++) out[i] = Math.fround(src[i]);
56861
+ return out;
56862
+ }
56863
+ function singleApply(v) {
56864
+ if (isRuntimeChar(v)) {
56865
+ if (v.value.length === 0) return RTV.tensor(allocFloat64Array(0), [0, 0]);
56866
+ if (v.value.length === 1)
56867
+ return RTV.num(Math.fround(v.value.charCodeAt(0)));
56868
+ return RTV.row(Array.from(v.value).map((c) => Math.fround(c.charCodeAt(0))));
56869
+ }
56870
+ if (isRuntimeLogical(v)) return RTV.num(v ? 1 : 0);
56871
+ if (isRuntimeNumber(v)) return RTV.num(Math.fround(v));
56872
+ if (isRuntimeComplexNumber(v))
56873
+ return RTV.complex(Math.fround(v.re), Math.fround(v.im));
56874
+ if (isRuntimeTensor(v)) {
56875
+ const imag2 = v.imag ? froundArray(v.imag) : void 0;
56876
+ return RTV.tensor(froundArray(v.data), v.shape.slice(), imag2);
56877
+ }
56878
+ if (isRuntimeClassInstance(v) && v._builtinData !== void 0)
56879
+ return singleApply(v._builtinData);
56880
+ return RTV.num(Math.fround(toNumber(v)));
56881
+ }
56882
+ defineBuiltin({
56883
+ name: "single",
56884
+ cases: [
56885
+ {
56886
+ match: (argTypes) => {
56887
+ if (argTypes.length !== 1) return null;
56888
+ const a = argTypes[0];
56889
+ if (a.kind === "number" || a.kind === "boolean" || a.kind === "char" || a.kind === "class_instance")
56890
+ return [{ kind: "number" }];
56891
+ if (a.kind === "complex_or_number")
56892
+ return [{ kind: "complex_or_number" }];
56893
+ if (a.kind === "tensor")
56894
+ return [
56895
+ {
56896
+ kind: "tensor",
56897
+ isComplex: a.isComplex,
56898
+ shape: a.shape,
56899
+ ndim: a.ndim
56900
+ }
56901
+ ];
56902
+ return null;
56903
+ },
56904
+ apply: (args) => singleApply(args[0])
56905
+ }
56906
+ ]
56907
+ });
56060
56908
  var INT_RANGES = [
56061
56909
  { name: "int8", min: -128, max: 127 },
56062
56910
  { name: "int16", min: -32768, max: 32767 },
@@ -56611,11 +57459,20 @@ defineBuiltin({
56611
57459
  },
56612
57460
  apply: (args) => {
56613
57461
  const v = args[0];
56614
- if (isRuntimeStructArray(v))
56615
- return RTV.logical(v.fieldNames.includes(toString(args[1])));
56616
- if (!isRuntimeStruct(v) && !isRuntimeClassInstance(v))
56617
- return RTV.logical(false);
56618
- return RTV.logical(v.fields.has(toString(args[1])));
57462
+ const hasField = (name) => {
57463
+ if (isRuntimeStructArray(v)) return v.fieldNames.includes(name);
57464
+ if (!isRuntimeStruct(v) && !isRuntimeClassInstance(v)) return false;
57465
+ return v.fields.has(name);
57466
+ };
57467
+ if (isRuntimeCell(args[1])) {
57468
+ const cell = args[1];
57469
+ const result = allocFloat64Array(cell.data.length);
57470
+ for (let i = 0; i < cell.data.length; i++) {
57471
+ result[i] = hasField(toString(cell.data[i])) ? 1 : 0;
57472
+ }
57473
+ return new RuntimeTensor(result, cell.shape.slice(), void 0, true);
57474
+ }
57475
+ return RTV.logical(hasField(toString(args[1])));
56619
57476
  }
56620
57477
  }
56621
57478
  ]
@@ -56732,7 +57589,7 @@ function regexpImpl(caseSensitive, name, args, nargout) {
56732
57589
  if (outModes.length === 0) {
56733
57590
  outModes.push("start", "end", "tokenextents", "match", "tokens", "names");
56734
57591
  }
56735
- const flags = caseSensitive ? "g" : "gi";
57592
+ const flags = caseSensitive ? "gs" : "gis";
56736
57593
  const re = new RegExp(pat, flags);
56737
57594
  const starts = [];
56738
57595
  const ends = [];
@@ -57452,6 +58309,13 @@ registerIBuiltin({
57452
58309
  if (args.length === 1) return RTV.cell([A], [1, 1]);
57453
58310
  A = RTV.tensor(allocFloat64Array([A ? 1 : 0]), [1, 1]);
57454
58311
  }
58312
+ if (isRuntimeStruct(A)) {
58313
+ return RTV.cell([A], [1, 1]);
58314
+ }
58315
+ if (isRuntimeStructArray(A)) {
58316
+ const elems = A.elements;
58317
+ return RTV.cell([...elems], [1, elems.length]);
58318
+ }
57455
58319
  if (!isRuntimeTensor(A))
57456
58320
  throw new RuntimeError(
57457
58321
  "num2cell: first argument must be a numeric array"
@@ -57575,6 +58439,69 @@ registerIBuiltin({
57575
58439
  };
57576
58440
  }
57577
58441
  });
58442
+ registerIBuiltin({
58443
+ name: "getfield",
58444
+ resolve: (argTypes) => {
58445
+ if (argTypes.length < 2) return null;
58446
+ return {
58447
+ outputTypes: [{ kind: "unknown" }],
58448
+ apply: (args) => {
58449
+ let v = args[0];
58450
+ for (let i = 1; i < args.length; i++) {
58451
+ if (isRuntimeCell(args[i]))
58452
+ throw new RuntimeError(
58453
+ "getfield: index ({}) subscripts are not supported"
58454
+ );
58455
+ if (!isRuntimeStruct(v))
58456
+ throw new RuntimeError("getfield: argument must be a structure");
58457
+ const name = toString(args[i]);
58458
+ if (!v.fields.has(name))
58459
+ throw new RuntimeError(
58460
+ `Reference to non-existent field '${name}'.`
58461
+ );
58462
+ v = v.fields.get(name);
58463
+ }
58464
+ return v;
58465
+ }
58466
+ };
58467
+ }
58468
+ });
58469
+ registerIBuiltin({
58470
+ name: "setfield",
58471
+ resolve: (argTypes) => {
58472
+ if (argTypes.length < 3) return null;
58473
+ return {
58474
+ outputTypes: [{ kind: "struct", fields: {} }],
58475
+ apply: (args) => {
58476
+ const value = args[args.length - 1];
58477
+ const fieldArgs = args.slice(1, args.length - 1);
58478
+ const setChain = (s, depth) => {
58479
+ const fa = fieldArgs[depth];
58480
+ if (isRuntimeCell(fa))
58481
+ throw new RuntimeError(
58482
+ "setfield: index ({}) subscripts are not supported"
58483
+ );
58484
+ const name = toString(fa);
58485
+ const base = isRuntimeStruct(s) ? s.fields : /* @__PURE__ */ new Map();
58486
+ if (!isRuntimeStruct(s) && !(isRuntimeTensor(s) && s.data.length === 0))
58487
+ throw new RuntimeError("setfield: argument must be a structure");
58488
+ const newFields = new Map(base);
58489
+ if (depth === fieldArgs.length - 1) {
58490
+ newFields.set(name, value);
58491
+ } else {
58492
+ const child = newFields.get(name);
58493
+ newFields.set(
58494
+ name,
58495
+ setChain(child ?? RTV.struct(/* @__PURE__ */ new Map()), depth + 1)
58496
+ );
58497
+ }
58498
+ return RTV.struct(newFields);
58499
+ };
58500
+ return setChain(args[0], 0);
58501
+ }
58502
+ };
58503
+ }
58504
+ });
57578
58505
  registerIBuiltin({
57579
58506
  name: "namedargs2cell",
57580
58507
  resolve: (argTypes) => {
@@ -57596,6 +58523,10 @@ registerIBuiltin({
57596
58523
  };
57597
58524
  }
57598
58525
  });
58526
+ function rmfieldNames(arg) {
58527
+ if (isRuntimeCell(arg)) return arg.data.map(toString);
58528
+ return [toString(arg)];
58529
+ }
57599
58530
  registerIBuiltin({
57600
58531
  name: "rmfield",
57601
58532
  resolve: (argTypes) => {
@@ -57604,25 +58535,26 @@ registerIBuiltin({
57604
58535
  outputTypes: [{ kind: "struct", fields: {} }],
57605
58536
  apply: (args) => {
57606
58537
  const v = args[0];
58538
+ const names = rmfieldNames(args[1]);
57607
58539
  if (isRuntimeStructArray(v)) {
57608
- const name2 = toString(args[1]);
57609
- if (!v.fieldNames.includes(name2))
57610
- throw new RuntimeError(`rmfield: field '${name2}' does not exist`);
57611
- const newFieldNames = v.fieldNames.filter((n) => n !== name2);
58540
+ for (const name of names)
58541
+ if (!v.fieldNames.includes(name))
58542
+ throw new RuntimeError(`rmfield: field '${name}' does not exist`);
58543
+ const newFieldNames = v.fieldNames.filter((n) => !names.includes(n));
57612
58544
  const newElements = v.elements.map((el) => {
57613
58545
  const newFields2 = new Map(el.fields);
57614
- newFields2.delete(name2);
58546
+ for (const name of names) newFields2.delete(name);
57615
58547
  return RTV.struct(newFields2);
57616
58548
  });
57617
58549
  return RTV.structArray(newFieldNames, newElements);
57618
58550
  }
57619
58551
  if (!isRuntimeStruct(v))
57620
58552
  throw new RuntimeError("rmfield: first argument must be a struct");
57621
- const name = toString(args[1]);
57622
- if (!v.fields.has(name))
57623
- throw new RuntimeError(`rmfield: field '${name}' does not exist`);
58553
+ for (const name of names)
58554
+ if (!v.fields.has(name))
58555
+ throw new RuntimeError(`rmfield: field '${name}' does not exist`);
57624
58556
  const newFields = new Map(v.fields);
57625
- newFields.delete(name);
58557
+ for (const name of names) newFields.delete(name);
57626
58558
  return RTV.struct(newFields);
57627
58559
  }
57628
58560
  };
@@ -57843,6 +58775,23 @@ registerIBuiltin({
57843
58775
  }
57844
58776
  })
57845
58777
  });
58778
+ registerIBuiltin({
58779
+ name: "spalloc",
58780
+ resolve: () => ({
58781
+ outputTypes: [{ kind: "unknown" }],
58782
+ apply: (args) => {
58783
+ const m = args.length >= 1 ? Math.round(toNumber(args[0])) : 0;
58784
+ const n = args.length >= 2 ? Math.round(toNumber(args[1])) : 0;
58785
+ return RTV.sparseMatrix(
58786
+ m,
58787
+ n,
58788
+ new Int32Array(0),
58789
+ new Int32Array(n + 1),
58790
+ allocFloat64Array(0)
58791
+ );
58792
+ }
58793
+ })
58794
+ });
57846
58795
  registerIBuiltin({
57847
58796
  name: "speye",
57848
58797
  resolve: () => ({
@@ -58128,6 +59077,598 @@ registerIBuiltin({
58128
59077
  }
58129
59078
  })
58130
59079
  });
59080
+ var SPPARMS_KEYS = [
59081
+ "spumoni",
59082
+ "thr_rel",
59083
+ "thr_abs",
59084
+ "exact_d",
59085
+ "supernd",
59086
+ "rreduce",
59087
+ "wh_frac",
59088
+ "autommd",
59089
+ "autoamd",
59090
+ "piv_tol",
59091
+ "bandden",
59092
+ "umfpack",
59093
+ "sym_tol",
59094
+ "ldl_tol",
59095
+ "usema57",
59096
+ "spqrtol",
59097
+ "sp_ctor",
59098
+ "reorder",
59099
+ "no_redo"
59100
+ ];
59101
+ var SPPARMS_DEFAULTS = [
59102
+ 0,
59103
+ 1.1,
59104
+ 1,
59105
+ 0,
59106
+ 3,
59107
+ 3,
59108
+ 0.5,
59109
+ 1,
59110
+ 1,
59111
+ 0.1,
59112
+ 0.5,
59113
+ 1,
59114
+ 1e-3,
59115
+ 0.01,
59116
+ 1,
59117
+ -2,
59118
+ 0,
59119
+ 0,
59120
+ 0
59121
+ ];
59122
+ var spparmsState = Float64Array.from(SPPARMS_DEFAULTS);
59123
+ function spparmsVector() {
59124
+ return RTV.tensor(allocFloat64Array(Array.from(spparmsState)), [
59125
+ spparmsState.length,
59126
+ 1
59127
+ ]);
59128
+ }
59129
+ registerIBuiltin({
59130
+ name: "spparms",
59131
+ resolve: (argTypes, nargout) => {
59132
+ if (argTypes.length > 2) return null;
59133
+ const tensorOut = { kind: "tensor", isComplex: false };
59134
+ const outputTypes = nargout >= 2 ? [{ kind: "char" }, tensorOut] : [tensorOut];
59135
+ return {
59136
+ outputTypes,
59137
+ apply: (args, n) => {
59138
+ if (args.length === 0) {
59139
+ if (n >= 2) {
59140
+ const width = Math.max(...SPPARMS_KEYS.map((k) => k.length));
59141
+ const padded = SPPARMS_KEYS.map((k) => k.padEnd(width)).join("");
59142
+ return [
59143
+ new RuntimeChar(padded, [SPPARMS_KEYS.length, width]),
59144
+ spparmsVector()
59145
+ ];
59146
+ }
59147
+ return spparmsVector();
59148
+ }
59149
+ const first = args[0];
59150
+ if (isRuntimeChar(first) || isRuntimeString(first)) {
59151
+ const key = parseStringArgLower(first);
59152
+ if (args.length >= 2) {
59153
+ const idx2 = SPPARMS_KEYS.indexOf(key);
59154
+ if (idx2 >= 0) spparmsState[idx2] = toNumber(args[1]);
59155
+ return RTV.num(0);
59156
+ }
59157
+ if (key === "default") {
59158
+ spparmsState = Float64Array.from(SPPARMS_DEFAULTS);
59159
+ return RTV.num(0);
59160
+ }
59161
+ if (key === "tight") {
59162
+ return RTV.num(0);
59163
+ }
59164
+ const idx = SPPARMS_KEYS.indexOf(key);
59165
+ if (idx >= 0) return RTV.num(spparmsState[idx]);
59166
+ throw new RuntimeError(`spparms: unknown parameter '${key}'`);
59167
+ }
59168
+ const vals = isRuntimeNumber(first) ? [first] : isRuntimeTensor(first) ? Array.from(first.data) : null;
59169
+ if (vals === null)
59170
+ throw new RuntimeError(
59171
+ "spparms: argument must be a parameter name or value vector"
59172
+ );
59173
+ const next = Float64Array.from(spparmsState);
59174
+ for (let i = 0; i < Math.min(vals.length, next.length); i++)
59175
+ next[i] = vals[i];
59176
+ spparmsState = next;
59177
+ return RTV.num(0);
59178
+ }
59179
+ };
59180
+ }
59181
+ });
59182
+
59183
+ // src/numbl-core/interpreter/builtins/graph.ts
59184
+ function isGraph(v) {
59185
+ return isRuntimeClassInstance(v) && v.className === "graph";
59186
+ }
59187
+ function requireGraph(v, fn) {
59188
+ if (!isGraph(v)) {
59189
+ throw new RuntimeError(`${fn}: first argument must be a graph`);
59190
+ }
59191
+ return v;
59192
+ }
59193
+ function graphN(g) {
59194
+ return toNumber(g.fields.get("_n") ?? 0);
59195
+ }
59196
+ function graphWeighted(g) {
59197
+ const w = g.fields.get("_weighted");
59198
+ return w === true;
59199
+ }
59200
+ function graphAdj(g) {
59201
+ const a = g.fields.get("_A");
59202
+ if (a === void 0 || !isRuntimeSparseMatrix(a)) {
59203
+ throw new RuntimeError("graph: corrupt internal adjacency");
59204
+ }
59205
+ return a;
59206
+ }
59207
+ function buildSymAdj(n, edges) {
59208
+ const triplets = [];
59209
+ for (const e of edges) {
59210
+ if (e.w === 0) continue;
59211
+ const u = e.u - 1;
59212
+ const v = e.v - 1;
59213
+ if (u === v) {
59214
+ triplets.push({ col: u, row: u, val: e.w });
59215
+ } else {
59216
+ triplets.push({ col: v, row: u, val: e.w });
59217
+ triplets.push({ col: u, row: v, val: e.w });
59218
+ }
59219
+ }
59220
+ triplets.sort((a, b) => a.col - b.col || a.row - b.row);
59221
+ const ir = [];
59222
+ const pr = [];
59223
+ const cols = [];
59224
+ let prevCol = -1;
59225
+ let prevRow = -1;
59226
+ for (const t of triplets) {
59227
+ if (t.col === prevCol && t.row === prevRow) {
59228
+ pr[pr.length - 1] += t.val;
59229
+ } else {
59230
+ ir.push(t.row);
59231
+ pr.push(t.val);
59232
+ cols.push(t.col);
59233
+ prevCol = t.col;
59234
+ prevRow = t.row;
59235
+ }
59236
+ }
59237
+ const jc = new Int32Array(n + 1);
59238
+ let ci = 0;
59239
+ for (let c = 0; c < n; c++) {
59240
+ jc[c] = ci;
59241
+ while (ci < cols.length && cols[ci] === c) ci++;
59242
+ }
59243
+ jc[n] = ci;
59244
+ return RTV.sparseMatrix(n, n, new Int32Array(ir), jc, allocFloat64Array(pr));
59245
+ }
59246
+ function makeGraph(n, edges, weighted) {
59247
+ const fields = /* @__PURE__ */ new Map();
59248
+ fields.set("_n", n);
59249
+ fields.set("_A", buildSymAdj(n, edges));
59250
+ fields.set("_weighted", weighted);
59251
+ return new RuntimeClassInstance("graph", fields, false);
59252
+ }
59253
+ function eachNeighbor(A, c, cb) {
59254
+ for (let k = A.jc[c]; k < A.jc[c + 1]; k++) {
59255
+ const r = A.ir[k];
59256
+ if (r !== c) cb(r, A.pr[k]);
59257
+ }
59258
+ }
59259
+ function edgesFromAdj(A) {
59260
+ const edges = [];
59261
+ for (let c = 0; c < A.n; c++) {
59262
+ for (let k = A.jc[c]; k < A.jc[c + 1]; k++) {
59263
+ const r = A.ir[k];
59264
+ if (r < c) edges.push({ u: r + 1, v: c + 1, w: A.pr[k] });
59265
+ else if (r === c) edges.push({ u: r + 1, v: r + 1, w: A.pr[k] });
59266
+ }
59267
+ }
59268
+ return edges;
59269
+ }
59270
+ function squareDim(A, fn) {
59271
+ if (isRuntimeNumber(A) || isRuntimeLogical(A)) return 1;
59272
+ if (isRuntimeSparseMatrix(A)) {
59273
+ if (A.m !== A.n) throw new RuntimeError(`${fn}: adjacency must be square`);
59274
+ return A.n;
59275
+ }
59276
+ if (isRuntimeTensor(A)) {
59277
+ const rows = A.shape[0] ?? 1;
59278
+ const cols = A.shape[1] ?? 1;
59279
+ if (rows !== cols)
59280
+ throw new RuntimeError(`${fn}: adjacency must be square`);
59281
+ return rows;
59282
+ }
59283
+ throw new RuntimeError(`${fn}: adjacency must be a numeric matrix`);
59284
+ }
59285
+ function takeEntry(r, c, triangle) {
59286
+ if (r === c) return true;
59287
+ if (triangle === "lower") return r > c;
59288
+ return r < c;
59289
+ }
59290
+ function adjToEdges(A, omitSelfLoops, triangle, fn) {
59291
+ const n = squareDim(A, fn);
59292
+ const weighted = !(isRuntimeTensor(A) && A._isLogical);
59293
+ const edges = [];
59294
+ const push = (r, c, val) => {
59295
+ if (val === 0) return;
59296
+ if (r === c) {
59297
+ if (!omitSelfLoops) edges.push({ u: r + 1, v: r + 1, w: val });
59298
+ } else if (takeEntry(r, c, triangle)) {
59299
+ edges.push({ u: Math.min(r, c) + 1, v: Math.max(r, c) + 1, w: val });
59300
+ }
59301
+ };
59302
+ if (isRuntimeSparseMatrix(A)) {
59303
+ for (let c = 0; c < A.n; c++) {
59304
+ for (let k = A.jc[c]; k < A.jc[c + 1]; k++) push(A.ir[k], c, A.pr[k]);
59305
+ }
59306
+ } else if (isRuntimeTensor(A)) {
59307
+ const m = A.shape[0] ?? 1;
59308
+ for (let c = 0; c < n; c++) {
59309
+ for (let r = 0; r < n; r++) {
59310
+ const val = A.data[c * m + r];
59311
+ if (val !== 0) push(r, c, val);
59312
+ }
59313
+ }
59314
+ } else if (isRuntimeNumber(A) || isRuntimeLogical(A)) {
59315
+ const val = isRuntimeNumber(A) ? A : A ? 1 : 0;
59316
+ if (val !== 0) push(0, 0, val);
59317
+ }
59318
+ return { n, edges, weighted };
59319
+ }
59320
+ function isNumericArg3(v) {
59321
+ return isRuntimeNumber(v) || isRuntimeLogical(v) || isRuntimeTensor(v) || isRuntimeSparseMatrix(v);
59322
+ }
59323
+ function toNumArray2(v) {
59324
+ if (isRuntimeNumber(v)) return [v];
59325
+ if (isRuntimeLogical(v)) return [v ? 1 : 0];
59326
+ if (isRuntimeTensor(v)) return Array.from(v.data);
59327
+ throw new RuntimeError("graph: node/weight arguments must be numeric");
59328
+ }
59329
+ function argString(v) {
59330
+ if (isRuntimeString(v)) return v;
59331
+ if (isRuntimeChar(v)) return v.value;
59332
+ return null;
59333
+ }
59334
+ registerIBuiltin({
59335
+ name: "graph",
59336
+ help: {
59337
+ signatures: [
59338
+ "G = graph(A)",
59339
+ "G = graph(A, 'omitselfloops')",
59340
+ "G = graph(A, 'upper' | 'lower')",
59341
+ "G = graph(s, t)",
59342
+ "G = graph(s, t, w)",
59343
+ "G = graph(s, t, w, num)"
59344
+ ],
59345
+ 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."
59346
+ },
59347
+ resolve: () => ({
59348
+ outputTypes: [
59349
+ {
59350
+ kind: "class_instance",
59351
+ className: "graph",
59352
+ isHandleClass: false,
59353
+ fields: {}
59354
+ }
59355
+ ],
59356
+ apply: (args) => {
59357
+ if (args.length === 0) return makeGraph(0, [], false);
59358
+ if (args.length >= 2 && isNumericArg3(args[1])) {
59359
+ const s = toNumArray2(args[0]);
59360
+ const t = toNumArray2(args[1]);
59361
+ if (s.length !== t.length) {
59362
+ throw new RuntimeError("graph: s and t must have the same length");
59363
+ }
59364
+ let weighted2 = false;
59365
+ let w = null;
59366
+ let num = null;
59367
+ let omitSelfLoops2 = false;
59368
+ if (args.length >= 3 && isNumericArg3(args[2])) {
59369
+ w = toNumArray2(args[2]);
59370
+ weighted2 = true;
59371
+ if (args.length >= 4 && isNumericArg3(args[3])) {
59372
+ num = Math.floor(toNumber(args[3]));
59373
+ }
59374
+ }
59375
+ for (let i = 2; i < args.length; i++) {
59376
+ const str = argString(args[i]);
59377
+ if (str && str.toLowerCase() === "omitselfloops")
59378
+ omitSelfLoops2 = true;
59379
+ }
59380
+ let n2 = num ?? 0;
59381
+ for (let i = 0; i < s.length; i++) n2 = Math.max(n2, s[i], t[i]);
59382
+ const edges2 = [];
59383
+ for (let i = 0; i < s.length; i++) {
59384
+ const u = Math.min(s[i], t[i]);
59385
+ const v = Math.max(s[i], t[i]);
59386
+ if (omitSelfLoops2 && u === v) continue;
59387
+ edges2.push({ u, v, w: w ? w[w.length === 1 ? 0 : i] : 1 });
59388
+ }
59389
+ return makeGraph(n2, edges2, weighted2);
59390
+ }
59391
+ let omitSelfLoops = false;
59392
+ let triangle = "sym";
59393
+ for (let i = 1; i < args.length; i++) {
59394
+ const str = argString(args[i]);
59395
+ if (!str) continue;
59396
+ const lc = str.toLowerCase();
59397
+ if (lc === "omitselfloops") omitSelfLoops = true;
59398
+ else if (lc === "upper") triangle = "upper";
59399
+ else if (lc === "lower") triangle = "lower";
59400
+ }
59401
+ const { n, edges, weighted } = adjToEdges(
59402
+ args[0],
59403
+ omitSelfLoops,
59404
+ triangle,
59405
+ "graph"
59406
+ );
59407
+ return makeGraph(n, edges, weighted);
59408
+ }
59409
+ })
59410
+ });
59411
+ registerIBuiltin({
59412
+ name: "conncomp",
59413
+ help: {
59414
+ signatures: ["bins = conncomp(G)", "[bins, binsizes] = conncomp(G)"],
59415
+ 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."
59416
+ },
59417
+ resolve: () => ({
59418
+ outputTypes: [{ kind: "unknown" }, { kind: "unknown" }],
59419
+ apply: (args, nargout) => {
59420
+ const g = requireGraph(args[0], "conncomp");
59421
+ const n = graphN(g);
59422
+ const A = graphAdj(g);
59423
+ const bins = new Float64Array(n);
59424
+ const sizes = [];
59425
+ let comp = 0;
59426
+ const stack = [];
59427
+ for (let start = 0; start < n; start++) {
59428
+ if (bins[start] !== 0) continue;
59429
+ comp++;
59430
+ let count = 0;
59431
+ bins[start] = comp;
59432
+ stack.length = 0;
59433
+ stack.push(start);
59434
+ while (stack.length > 0) {
59435
+ const node = stack.pop();
59436
+ count++;
59437
+ eachNeighbor(A, node, (r) => {
59438
+ if (bins[r] === 0) {
59439
+ bins[r] = comp;
59440
+ stack.push(r);
59441
+ }
59442
+ });
59443
+ }
59444
+ sizes.push(count);
59445
+ }
59446
+ const binsT = RTV.tensor(bins, [1, n]);
59447
+ if (nargout >= 2) {
59448
+ return [binsT, RTV.tensor(allocFloat64Array(sizes), [1, sizes.length])];
59449
+ }
59450
+ return binsT;
59451
+ }
59452
+ })
59453
+ });
59454
+ registerIBuiltin({
59455
+ name: "laplacian",
59456
+ help: {
59457
+ signatures: ["L = laplacian(G)"],
59458
+ 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."
59459
+ },
59460
+ resolve: () => ({
59461
+ outputTypes: [{ kind: "unknown" }],
59462
+ apply: (args) => {
59463
+ const g = requireGraph(args[0], "laplacian");
59464
+ const n = graphN(g);
59465
+ const A = graphAdj(g);
59466
+ const edges = [];
59467
+ for (let c = 0; c < n; c++) {
59468
+ let degree = 0;
59469
+ eachNeighbor(A, c, (r) => {
59470
+ degree++;
59471
+ if (r < c) edges.push({ u: r + 1, v: c + 1, w: -1 });
59472
+ });
59473
+ edges.push({ u: c + 1, v: c + 1, w: degree });
59474
+ }
59475
+ return buildSymAdj(n, edges);
59476
+ }
59477
+ })
59478
+ });
59479
+ registerIBuiltin({
59480
+ name: "addedge",
59481
+ help: {
59482
+ signatures: ["H = addedge(G, s, t)", "H = addedge(G, s, t, w)"],
59483
+ 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."
59484
+ },
59485
+ resolve: () => ({
59486
+ outputTypes: [
59487
+ {
59488
+ kind: "class_instance",
59489
+ className: "graph",
59490
+ isHandleClass: false,
59491
+ fields: {}
59492
+ }
59493
+ ],
59494
+ apply: (args) => {
59495
+ const g = requireGraph(args[0], "addedge");
59496
+ if (args.length < 3) {
59497
+ throw new RuntimeError("addedge: requires nodes s and t");
59498
+ }
59499
+ const weighted = graphWeighted(g);
59500
+ const s = toNumArray2(args[1]);
59501
+ const t = toNumArray2(args[2]);
59502
+ if (s.length !== t.length) {
59503
+ throw new RuntimeError("addedge: s and t must have the same length");
59504
+ }
59505
+ let w = null;
59506
+ if (args.length >= 4) {
59507
+ w = toNumArray2(args[3]);
59508
+ } else if (weighted) {
59509
+ throw new RuntimeError(
59510
+ "addedge: Must specify weights when adding an edge to a weighted graph."
59511
+ );
59512
+ }
59513
+ const edges = edgesFromAdj(graphAdj(g));
59514
+ let n = graphN(g);
59515
+ for (let i = 0; i < s.length; i++) {
59516
+ const u = Math.min(s[i], t[i]);
59517
+ const v = Math.max(s[i], t[i]);
59518
+ n = Math.max(n, u, v);
59519
+ edges.push({ u, v, w: w ? w[w.length === 1 ? 0 : i] : 1 });
59520
+ }
59521
+ return makeGraph(n, edges, weighted);
59522
+ }
59523
+ })
59524
+ });
59525
+ registerIBuiltin({
59526
+ name: "numnodes",
59527
+ help: {
59528
+ signatures: ["n = numnodes(G)"],
59529
+ description: "Number of nodes in graph G."
59530
+ },
59531
+ resolve: () => ({
59532
+ outputTypes: [{ kind: "number" }],
59533
+ apply: (args) => RTV.num(graphN(requireGraph(args[0], "numnodes")))
59534
+ })
59535
+ });
59536
+ registerIBuiltin({
59537
+ name: "numedges",
59538
+ help: {
59539
+ signatures: ["m = numedges(G)"],
59540
+ description: "Number of edges in graph G."
59541
+ },
59542
+ resolve: () => ({
59543
+ outputTypes: [{ kind: "number" }],
59544
+ apply: (args) => {
59545
+ const g = requireGraph(args[0], "numedges");
59546
+ return RTV.num(edgesFromAdj(graphAdj(g)).length);
59547
+ }
59548
+ })
59549
+ });
59550
+ registerIBuiltin({
59551
+ name: "degree",
59552
+ help: {
59553
+ signatures: ["d = degree(G)"],
59554
+ description: "Degree of each node in graph G, returned as a column vector of edge counts (self-loops excluded)."
59555
+ },
59556
+ resolve: () => ({
59557
+ outputTypes: [{ kind: "unknown" }],
59558
+ apply: (args) => {
59559
+ const g = requireGraph(args[0], "degree");
59560
+ const n = graphN(g);
59561
+ const A = graphAdj(g);
59562
+ const d = new Float64Array(n);
59563
+ for (let c = 0; c < n; c++) eachNeighbor(A, c, () => d[c] += 1);
59564
+ return RTV.tensor(d, [n, 1]);
59565
+ }
59566
+ })
59567
+ });
59568
+
59569
+ // src/numbl-core/interpreter/builtins/gallery.ts
59570
+ function toNumArray3(v, what) {
59571
+ if (isRuntimeNumber(v)) return [v];
59572
+ if (isRuntimeLogical(v)) return [v ? 1 : 0];
59573
+ if (isRuntimeTensor(v)) return Array.from(v.data);
59574
+ if (isRuntimeComplexNumber(v)) return [v.re];
59575
+ throw new RuntimeError(`gallery: ${what} must be numeric`);
59576
+ }
59577
+ function buildTridiag(sub2, diag2, sup) {
59578
+ const n = diag2.length;
59579
+ if (sub2.length !== n - 1 || sup.length !== n - 1) {
59580
+ throw new RuntimeError(
59581
+ "gallery: tridiag sub/superdiagonal must have length one less than the diagonal"
59582
+ );
59583
+ }
59584
+ const ir = [];
59585
+ const pr = [];
59586
+ const jc = new Int32Array(n + 1);
59587
+ for (let c = 0; c < n; c++) {
59588
+ jc[c] = ir.length;
59589
+ if (c >= 1 && sup[c - 1] !== 0) {
59590
+ ir.push(c - 1);
59591
+ pr.push(sup[c - 1]);
59592
+ }
59593
+ if (diag2[c] !== 0) {
59594
+ ir.push(c);
59595
+ pr.push(diag2[c]);
59596
+ }
59597
+ if (c <= n - 2 && sub2[c] !== 0) {
59598
+ ir.push(c + 1);
59599
+ pr.push(sub2[c]);
59600
+ }
59601
+ }
59602
+ jc[n] = ir.length;
59603
+ return RTV.sparseMatrix(n, n, new Int32Array(ir), jc, allocFloat64Array(pr));
59604
+ }
59605
+ function buildTridiagToeplitz(n, c, d, e) {
59606
+ const off = Math.max(0, n - 1);
59607
+ return buildTridiag(
59608
+ new Array(off).fill(c),
59609
+ new Array(n).fill(d),
59610
+ new Array(off).fill(e)
59611
+ );
59612
+ }
59613
+ function galleryTridiag(rest) {
59614
+ if (rest.length === 1) {
59615
+ return buildTridiagToeplitz(Math.round(toNumber(rest[0])), -1, 2, -1);
59616
+ }
59617
+ if (rest.length === 4) {
59618
+ return buildTridiagToeplitz(
59619
+ Math.round(toNumber(rest[0])),
59620
+ toNumber(rest[1]),
59621
+ toNumber(rest[2]),
59622
+ toNumber(rest[3])
59623
+ );
59624
+ }
59625
+ if (rest.length === 3) {
59626
+ const sub2 = toNumArray3(rest[0], "subdiagonal");
59627
+ const diag2 = toNumArray3(rest[1], "diagonal");
59628
+ const sup = toNumArray3(rest[2], "superdiagonal");
59629
+ if (sub2.length === 1 && diag2.length === 1 && sup.length === 1) {
59630
+ return buildTridiag([], diag2, []);
59631
+ }
59632
+ return buildTridiag(sub2, diag2, sup);
59633
+ }
59634
+ throw new RuntimeError("gallery: tridiag expects (n), (n,c,d,e), or (x,y,z)");
59635
+ }
59636
+ registerIBuiltin({
59637
+ name: "gallery",
59638
+ help: {
59639
+ signatures: [
59640
+ "A = gallery('tridiag', n)",
59641
+ "A = gallery('tridiag', n, c, d, e)",
59642
+ "A = gallery('tridiag', x, y, z)"
59643
+ ],
59644
+ 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."
59645
+ },
59646
+ resolve: () => ({
59647
+ outputTypes: [{ kind: "unknown" }],
59648
+ apply: (args) => {
59649
+ if (args.length < 1) {
59650
+ throw new RuntimeError("gallery: not enough input arguments");
59651
+ }
59652
+ const name = parseStringArgLower(args[0]);
59653
+ let rest = args.slice(1);
59654
+ if (rest.length >= 1) {
59655
+ const last = rest[rest.length - 1];
59656
+ if (isRuntimeString(last) || isRuntimeChar(last)) {
59657
+ const cn = parseStringArgLower(last);
59658
+ if (cn === "single" || cn === "double") rest = rest.slice(0, -1);
59659
+ }
59660
+ }
59661
+ switch (name) {
59662
+ case "tridiag":
59663
+ return galleryTridiag(rest);
59664
+ default:
59665
+ throw new RuntimeError(
59666
+ `gallery: matrix family '${name}' is not supported`
59667
+ );
59668
+ }
59669
+ }
59670
+ })
59671
+ });
58131
59672
 
58132
59673
  // src/numbl-core/interpreter/builtins/special-math.ts
58133
59674
  function binaryApply(a, b, fn) {
@@ -58611,12 +60152,19 @@ registerIBuiltin({
58611
60152
 
58612
60153
  // src/numbl-core/native/geometry-bridge.ts
58613
60154
  var backend = null;
60155
+ var hullBackend = null;
58614
60156
  function setDelaunayBackend(fn) {
58615
60157
  backend = fn;
58616
60158
  }
58617
60159
  function getDelaunayBackend() {
58618
60160
  return backend;
58619
60161
  }
60162
+ function setConvexHullBackend(fn) {
60163
+ hullBackend = fn;
60164
+ }
60165
+ function getConvexHullBackend() {
60166
+ return hullBackend;
60167
+ }
58620
60168
 
58621
60169
  // src/numbl-core/interpreter/builtins/geometry.ts
58622
60170
  function toFlatArray(v, name) {
@@ -58669,7 +60217,7 @@ function simplexVolume(points, cell, dim) {
58669
60217
  for (let k = 2; k <= dim; k++) fact *= k;
58670
60218
  return Math.abs(det) / fact;
58671
60219
  }
58672
- function triangulateToTensor(points, dim) {
60220
+ function triangulateToTensor(points, dim, orientCCW = false) {
58673
60221
  const backend2 = getDelaunayBackend();
58674
60222
  if (!backend2)
58675
60223
  throw new RuntimeError(
@@ -58689,6 +60237,16 @@ function triangulateToTensor(points, dim) {
58689
60237
  const cells = volTol > 0 ? raw.filter((cell) => simplexVolume(points, cell, dim) > volTol) : raw;
58690
60238
  const numCells = cells.length;
58691
60239
  const cols = dim + 1;
60240
+ if (orientCCW && dim === 2) {
60241
+ for (const cell of cells) {
60242
+ const [a, b, c] = cell;
60243
+ 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]);
60244
+ if (signedArea2 < 0) {
60245
+ cell[1] = c;
60246
+ cell[2] = b;
60247
+ }
60248
+ }
60249
+ }
58692
60250
  const out = allocFloat64Array(numCells * cols);
58693
60251
  for (let i = 0; i < numCells; i++) {
58694
60252
  const cell = cells[i];
@@ -58742,7 +60300,7 @@ defineBuiltin({
58742
60300
  throw new RuntimeError(
58743
60301
  `delaunay: need at least ${dim + 1} points for a ${dim}-D triangulation`
58744
60302
  );
58745
- return triangulateToTensor(points, dim);
60303
+ return triangulateToTensor(points, dim, true);
58746
60304
  }
58747
60305
  }
58748
60306
  ]
@@ -58785,6 +60343,203 @@ defineBuiltin({
58785
60343
  }
58786
60344
  ]
58787
60345
  });
60346
+ var CONVHULLN_MAX_DIM = 3;
60347
+ function convexHullFacets(points, dim, name) {
60348
+ const backend2 = getConvexHullBackend();
60349
+ if (!backend2)
60350
+ throw new RuntimeError(
60351
+ `${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.`
60352
+ );
60353
+ return backend2(points, dim);
60354
+ }
60355
+ function hullInteriorPoint(points, facets, dim) {
60356
+ const used = /* @__PURE__ */ new Set();
60357
+ for (const f of facets) for (const idx of f) used.add(idx);
60358
+ const c = new Array(dim).fill(0);
60359
+ for (const idx of used) {
60360
+ const p2 = points[idx];
60361
+ for (let k = 0; k < dim; k++) c[k] += p2[k];
60362
+ }
60363
+ const m = used.size || 1;
60364
+ for (let k = 0; k < dim; k++) c[k] /= m;
60365
+ return c;
60366
+ }
60367
+ function hullMeasure(points, facets, dim) {
60368
+ const c = hullInteriorPoint(points, facets, dim);
60369
+ let total = 0;
60370
+ if (dim === 2) {
60371
+ for (const f of facets) {
60372
+ const a = points[f[0]];
60373
+ const b = points[f[1]];
60374
+ const ax = a[0] - c[0], ay = a[1] - c[1];
60375
+ const bx = b[0] - c[0], by = b[1] - c[1];
60376
+ total += Math.abs(ax * by - ay * bx) / 2;
60377
+ }
60378
+ } else {
60379
+ for (const f of facets) {
60380
+ const a = points[f[0]];
60381
+ const b = points[f[1]];
60382
+ const d = points[f[2]];
60383
+ const ax = a[0] - c[0], ay = a[1] - c[1], az = a[2] - c[2];
60384
+ const bx = b[0] - c[0], by = b[1] - c[1], bz = b[2] - c[2];
60385
+ const dx = d[0] - c[0], dy = d[1] - c[1], dz = d[2] - c[2];
60386
+ const det = ax * (by * dz - bz * dy) - ay * (bx * dz - bz * dx) + az * (bx * dy - by * dx);
60387
+ total += Math.abs(det) / 6;
60388
+ }
60389
+ }
60390
+ return total;
60391
+ }
60392
+ function facetsToTensor(facets, cols) {
60393
+ const n = facets.length;
60394
+ const out = allocFloat64Array(n * cols);
60395
+ for (let i = 0; i < n; i++) {
60396
+ const f = facets[i];
60397
+ for (let j = 0; j < cols; j++) out[j * n + i] = f[j] + 1;
60398
+ }
60399
+ return RTV.tensor(out, [n, cols]);
60400
+ }
60401
+ function orderHull2D(facets, points) {
60402
+ const adj = /* @__PURE__ */ new Map();
60403
+ for (const [a, b] of facets) {
60404
+ (adj.get(a) ?? adj.set(a, []).get(a)).push(b);
60405
+ (adj.get(b) ?? adj.set(b, []).get(b)).push(a);
60406
+ }
60407
+ const start = facets[0][0];
60408
+ const loop = [start];
60409
+ let prev = -1;
60410
+ let cur = start;
60411
+ for (let guard = 0; guard <= facets.length; guard++) {
60412
+ const nbrs = adj.get(cur);
60413
+ const next = nbrs[0] === prev ? nbrs[1] : nbrs[0];
60414
+ if (next === void 0 || next === start) break;
60415
+ loop.push(next);
60416
+ prev = cur;
60417
+ cur = next;
60418
+ }
60419
+ let signed = 0;
60420
+ for (let i = 0; i < loop.length; i++) {
60421
+ const p2 = points[loop[i]];
60422
+ const q = points[loop[(i + 1) % loop.length]];
60423
+ signed += p2[0] * q[1] - q[0] * p2[1];
60424
+ }
60425
+ if (signed < 0) loop.reverse();
60426
+ loop.push(loop[0]);
60427
+ const out = allocFloat64Array(loop.length);
60428
+ for (let i = 0; i < loop.length; i++) out[i] = loop[i] + 1;
60429
+ return RTV.tensor(out, [loop.length, 1]);
60430
+ }
60431
+ defineBuiltin({
60432
+ name: "convhull",
60433
+ help: {
60434
+ signatures: [
60435
+ "k = convhull(P)",
60436
+ "k = convhull(x,y)",
60437
+ "k = convhull(x,y,z)",
60438
+ "k = convhull(___,'Simplify',tf)",
60439
+ "[k,av] = convhull(___)"
60440
+ ],
60441
+ 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."
60442
+ },
60443
+ cases: [
60444
+ {
60445
+ match: (argTypes, nargout) => {
60446
+ if (nargout > 2) return null;
60447
+ if (argTypes.length < 1 || argTypes.length > 5) return null;
60448
+ const ok = (t) => t.kind === "number" || t.kind === "boolean" || t.kind === "tensor" || t.kind === "string" || t.kind === "char";
60449
+ if (!argTypes.every(ok)) return null;
60450
+ const k = { kind: "tensor", isComplex: false };
60451
+ return nargout > 1 ? [k, { kind: "number" }] : [k];
60452
+ },
60453
+ apply: (args, nargout) => {
60454
+ let coordArgs = args;
60455
+ const tail = args[args.length - 2];
60456
+ const isSimplifyName = (v) => typeof v === "string" && v.toLowerCase() === "simplify" || isRuntimeChar(v) && v.value.toLowerCase() === "simplify";
60457
+ if (args.length >= 3 && isSimplifyName(tail)) {
60458
+ coordArgs = args.slice(0, args.length - 2);
60459
+ }
60460
+ let points;
60461
+ let dim;
60462
+ if (coordArgs.length === 1) {
60463
+ const P = coordArgs[0];
60464
+ if (!isRuntimeTensor(P))
60465
+ throw new RuntimeError(
60466
+ "convhull: P must be an N-by-2 or N-by-3 matrix"
60467
+ );
60468
+ const [, d] = tensorSize2D(P);
60469
+ if (d !== 2 && d !== 3)
60470
+ throw new RuntimeError("convhull: P must have 2 or 3 columns");
60471
+ points = matrixToPoints(P, "convhull");
60472
+ dim = d;
60473
+ } else if (coordArgs.length === 2 || coordArgs.length === 3) {
60474
+ const coords = coordArgs.map((a) => toFlatArray(a, "convhull"));
60475
+ const n = coords[0].length;
60476
+ if (coords.some((c) => c.length !== n))
60477
+ throw new RuntimeError(
60478
+ "convhull: coordinate vectors must have the same length"
60479
+ );
60480
+ points = new Array(n);
60481
+ for (let i = 0; i < n; i++) points[i] = coords.map((c) => c[i]);
60482
+ dim = coordArgs.length;
60483
+ } else {
60484
+ throw new RuntimeError("convhull: invalid arguments");
60485
+ }
60486
+ if (points.length < dim + 1)
60487
+ throw new RuntimeError(
60488
+ `convhull: need at least ${dim + 1} points for a ${dim}-D hull`
60489
+ );
60490
+ const facets = convexHullFacets(points, dim, "convhull");
60491
+ const k = dim === 2 ? orderHull2D(facets, points) : facetsToTensor(facets, 3);
60492
+ if (nargout > 1) return [k, hullMeasure(points, facets, dim)];
60493
+ return k;
60494
+ }
60495
+ }
60496
+ ]
60497
+ });
60498
+ defineBuiltin({
60499
+ name: "convhulln",
60500
+ help: {
60501
+ signatures: [
60502
+ "k = convhulln(P)",
60503
+ "k = convhulln(P,opts)",
60504
+ "[k,vol] = convhulln(___)"
60505
+ ],
60506
+ 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)."
60507
+ },
60508
+ cases: [
60509
+ {
60510
+ match: (argTypes, nargout) => {
60511
+ if (nargout > 2) return null;
60512
+ if (argTypes.length < 1 || argTypes.length > 2) return null;
60513
+ const x = argTypes[0];
60514
+ if (x.kind !== "tensor" && x.kind !== "number" && x.kind !== "boolean")
60515
+ return null;
60516
+ const k = { kind: "tensor", isComplex: false };
60517
+ return nargout > 1 ? [k, { kind: "number" }] : [k];
60518
+ },
60519
+ apply: (args, nargout) => {
60520
+ const P = args[0];
60521
+ if (!isRuntimeTensor(P))
60522
+ throw new RuntimeError("convhulln: P must be an m-by-n matrix");
60523
+ const [m, n] = tensorSize2D(P);
60524
+ if (n < 2)
60525
+ throw new RuntimeError("convhulln: P must have at least 2 columns");
60526
+ if (n > CONVHULLN_MAX_DIM)
60527
+ throw new RuntimeError(
60528
+ `convhulln: only 2-D and 3-D hulls are supported (got ${n}-D); higher dimensions are not yet validated`
60529
+ );
60530
+ if (m < n + 1)
60531
+ throw new RuntimeError(
60532
+ `convhulln: need at least ${n + 1} points for a ${n}-D hull`
60533
+ );
60534
+ const points = matrixToPoints(P, "convhulln");
60535
+ const facets = convexHullFacets(points, n, "convhulln");
60536
+ const k = facetsToTensor(facets, n);
60537
+ if (nargout > 1) return [k, hullMeasure(points, facets, n)];
60538
+ return k;
60539
+ }
60540
+ }
60541
+ ]
60542
+ });
58788
60543
  function getQueryValues(v, name) {
58789
60544
  if (typeof v === "number") return { data: [v], shape: null };
58790
60545
  if (typeof v === "boolean") return { data: [v ? 1 : 0], shape: null };
@@ -59268,6 +61023,10 @@ var H = {
59268
61023
  signatures: ["C = class(A)"],
59269
61024
  description: "Returns the class name of A as a character vector."
59270
61025
  },
61026
+ superclasses: {
61027
+ signatures: ["S = superclasses(ClassName)", "S = superclasses(obj)"],
61028
+ 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."
61029
+ },
59271
61030
  fieldnames: {
59272
61031
  signatures: ["F = fieldnames(S)"],
59273
61032
  description: "Returns cell array of field names of struct or object S."
@@ -59671,6 +61430,14 @@ var H = {
59671
61430
  signatures: ["B = pinv(A)", "B = pinv(A, TOL)"],
59672
61431
  description: "Moore-Penrose pseudoinverse."
59673
61432
  },
61433
+ null: {
61434
+ signatures: ["Z = null(A)", "Z = null(A, TOL)"],
61435
+ description: "Orthonormal basis for the null space of A (columns of Z), computed via the SVD. TOL overrides the singular-value tolerance."
61436
+ },
61437
+ bandwidth: {
61438
+ signatures: ["[lower, upper] = bandwidth(A)", "B = bandwidth(A, TYPE)"],
61439
+ description: "Lower and upper matrix bandwidth. TYPE is 'lower' or 'upper'; a single output without TYPE returns the lower bandwidth."
61440
+ },
59674
61441
  cond: {
59675
61442
  signatures: ["C = cond(A)", "C = cond(A, P)"],
59676
61443
  description: "Condition number of matrix."
@@ -59900,6 +61667,10 @@ var H = {
59900
61667
  signatures: ["Y = double(X)"],
59901
61668
  description: "Convert to double precision."
59902
61669
  },
61670
+ single: {
61671
+ signatures: ["Y = single(X)"],
61672
+ 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."
61673
+ },
59903
61674
  logical: {
59904
61675
  signatures: ["Y = logical(X)"],
59905
61676
  description: "Convert to logical (boolean) array."
@@ -60270,6 +62041,18 @@ var H = {
60270
62041
  ],
60271
62042
  description: "Extract or create sparse banded/diagonal matrices."
60272
62043
  },
62044
+ spparms: {
62045
+ signatures: [
62046
+ "spparms",
62047
+ "values = spparms",
62048
+ "[keys, values] = spparms",
62049
+ "value = spparms(KEY)",
62050
+ "spparms(KEY, VALUE)",
62051
+ "spparms('default')",
62052
+ "spparms(values)"
62053
+ ],
62054
+ 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."
62055
+ },
60273
62056
  // ── Linear algebra extras ─────────────────────────────────────────────
60274
62057
  qz: {
60275
62058
  signatures: [
@@ -61071,7 +62854,7 @@ function getSourceLine(getSource, file, line) {
61071
62854
  }
61072
62855
 
61073
62856
  // src/numbl-core/version.ts
61074
- var NUMBL_VERSION = "0.4.6";
62857
+ var NUMBL_VERSION = "0.4.7";
61075
62858
 
61076
62859
  // src/cli-repl.ts
61077
62860
  import { createInterface } from "readline";
@@ -61537,6 +63320,7 @@ function resolveFunctionImpl(name, argTypes, callSite, index2) {
61537
63320
  for (const argType of argTypes) {
61538
63321
  if (argType?.kind === "ClassInstance") {
61539
63322
  const className = argType.className;
63323
+ if (index2.classConstructors.get(className) === name) continue;
61540
63324
  if (!candidates.includes(className) && (index2.classInstanceMethods.get(className)?.has(name) || index2.classStaticMethods.get(className)?.has(name))) {
61541
63325
  candidates.push(className);
61542
63326
  }
@@ -61645,12 +63429,25 @@ var Environment = class _Environment {
61645
63429
  }
61646
63430
  /** Function ID for persistent variable storage */
61647
63431
  persistentFuncId;
63432
+ /** Call-site variable names of this frame's arguments, for `inputname`.
63433
+ * Entry i is the name of the variable passed as argument i+1, or '' if
63434
+ * that argument was not a plain variable. Undefined when the call did
63435
+ * not originate from an interpreted call expression (e.g. feval, JIT).
63436
+ * Read directly off the executing frame — the interpreter has only
63437
+ * function-level scoping, so `this.env` is the frame while a body runs. */
63438
+ inputArgNames;
61648
63439
  /** Back-reference to the runtime (needed for global/persistent access) */
61649
63440
  rt = null;
61650
63441
  /** Set when a `@nestedFn` handle has been created that captures this env
61651
63442
  * (or an ancestor). Tells the function-exit cleanup that clearing this
61652
63443
  * env would strand the handle's closure, so locals must be left alive. */
61653
63444
  nestedHandleCreated = false;
63445
+ /** For a nested-function frame: the names of this function's own formal
63446
+ * input/output arguments. These are always local — a write must never be
63447
+ * redirected to a same-named variable in the parent, even before the
63448
+ * output has been assigned. (MATLAB scopes a nested function's formal
63449
+ * arguments to that function; only other variables are shared.) */
63450
+ nestedLocalNames;
61654
63451
  get(name) {
61655
63452
  if (this._globalNames !== void 0 && this._globalNames.has(name) && this.rt) {
61656
63453
  const v = this.rt.$g[name];
@@ -61669,7 +63466,7 @@ var Environment = class _Environment {
61669
63466
  if (old !== void 0) decref(this.rt, old);
61670
63467
  return;
61671
63468
  }
61672
- if (this.isNested && !this.vars.has(name) && this.parent) {
63469
+ if (this.isNested && !this.vars.has(name) && this.parent && !this.nestedLocalNames?.has(name)) {
61673
63470
  const owner = this.findOwner(name);
61674
63471
  if (owner) {
61675
63472
  owner.setLocal(name, value);
@@ -62864,6 +64661,7 @@ function makeRootContext(interp, registry3) {
62864
64661
  var interpreterExec_exports = {};
62865
64662
  __export(interpreterExec_exports, {
62866
64663
  assignLValue: () => assignLValue,
64664
+ computeInputNames: () => computeInputNames,
62867
64665
  evalAnonFunc: () => evalAnonFunc,
62868
64666
  evalArgs: () => evalArgs,
62869
64667
  evalBinary: () => evalBinary,
@@ -63166,6 +64964,11 @@ function execStmtInner(stmt) {
63166
64964
  case "Global": {
63167
64965
  for (const name of stmt.names) {
63168
64966
  this.env.globalNames.add(name);
64967
+ if (this.rt && !(name in this.rt.$g)) {
64968
+ const empty = RTV.tensor(allocFloat64Array(0), [0, 0]);
64969
+ incref(empty);
64970
+ this.rt.$g[name] = empty;
64971
+ }
63169
64972
  }
63170
64973
  return null;
63171
64974
  }
@@ -63277,6 +65080,10 @@ function evalExprNargout(expr, nargout) {
63277
65080
  if (isRuntimeStructArray(rv)) {
63278
65081
  return rv.elements.length;
63279
65082
  }
65083
+ if (isRuntimeClassInstanceArray(rv)) {
65084
+ if (ctx.numIndices === 1) return rv.elements.length;
65085
+ return ctx.dimIndex < rv.shape.length ? rv.shape[ctx.dimIndex] : 1;
65086
+ }
63280
65087
  if (isRuntimeSparseMatrix(rv)) {
63281
65088
  if (ctx.numIndices === 1) return rv.m * rv.n;
63282
65089
  return ctx.dimIndex === 0 ? rv.m : ctx.dimIndex === 1 ? rv.n : 1;
@@ -63437,6 +65244,23 @@ function evalExprNargout(expr, nargout) {
63437
65244
  throw new RuntimeError("Interpreter does not yet support meta.class");
63438
65245
  }
63439
65246
  }
65247
+ function computeInputNames(argExprs, callerEnv) {
65248
+ const names = [];
65249
+ let blanked = false;
65250
+ for (const a of argExprs) {
65251
+ if (blanked) {
65252
+ names.push("");
65253
+ } else if (a.type === "Ident" && callerEnv.has(a.name)) {
65254
+ names.push(a.name);
65255
+ } else if (a.type === "IndexCell" || a.type === "Member" || a.type === "MemberDynamic") {
65256
+ names.push("");
65257
+ blanked = true;
65258
+ } else {
65259
+ names.push("");
65260
+ }
65261
+ }
65262
+ return names;
65263
+ }
63440
65264
  function evalArgs(argExprs) {
63441
65265
  const args = [];
63442
65266
  for (const a of argExprs) {
@@ -63566,6 +65390,7 @@ function evalFuncCall(expr, nargout) {
63566
65390
  const c = getConstant(expr.name);
63567
65391
  if (c !== void 0) return c;
63568
65392
  }
65393
+ this.pendingInputNames = expr.args.length > 0 ? computeInputNames(expr.args, this.env) : void 0;
63569
65394
  return this.callFunction(expr.name, args, nargout);
63570
65395
  }
63571
65396
  function evalMember(expr, nargout) {
@@ -63745,7 +65570,14 @@ function makeFuncHandle(name) {
63745
65570
  );
63746
65571
  };
63747
65572
  fn.jsFnExpectsNargout = true;
63748
- const narg = getIBuiltinNargin(name);
65573
+ let narg = getIBuiltinNargin(name);
65574
+ if (narg === void 0) {
65575
+ try {
65576
+ narg = this.declaredNargin(name);
65577
+ } catch {
65578
+ narg = void 0;
65579
+ }
65580
+ }
63749
65581
  if (narg !== void 0) fn.nargin = narg;
63750
65582
  if (isNested) {
63751
65583
  fn.releaseExtra = () => capturedEnv.clearLocals();
@@ -64034,6 +65866,7 @@ __export(interpreterFunctions_exports, {
64034
65866
  callNestedFunction: () => callNestedFunction,
64035
65867
  callUserFunction: () => callUserFunction,
64036
65868
  collectClassProperties: () => collectClassProperties,
65869
+ declaredNargin: () => declaredNargin,
64037
65870
  evalInLocalScope: () => evalInLocalScope,
64038
65871
  findExternalMethod: () => findExternalMethod,
64039
65872
  findFunctionInClassFile: () => findFunctionInClassFile,
@@ -64336,6 +66169,30 @@ register("isa", (ctx, args) => {
64336
66169
  if (args.length !== 2) return FALL_THROUGH;
64337
66170
  return ctx.rt.isa(args[0], args[1]);
64338
66171
  });
66172
+ register("superclasses", (ctx, args) => {
66173
+ if (args.length !== 1) return FALL_THROUGH;
66174
+ const v = ensureRuntimeValue(args[0]);
66175
+ let className;
66176
+ if (isRuntimeClassInstance(v) || isRuntimeClassInstanceArray(v)) {
66177
+ className = v.className;
66178
+ } else if (isRuntimeChar(v) || isRuntimeString(v)) {
66179
+ className = toString(v);
66180
+ }
66181
+ const names = [];
66182
+ if (className) {
66183
+ const seen = /* @__PURE__ */ new Set([className]);
66184
+ let current = ctx.rt.getClassParentName(className);
66185
+ while (current && !seen.has(current)) {
66186
+ names.push(current);
66187
+ seen.add(current);
66188
+ current = ctx.rt.getClassParentName(current);
66189
+ }
66190
+ }
66191
+ return RTV.cell(
66192
+ names.map((n) => RTV.string(n)),
66193
+ [names.length, 1]
66194
+ );
66195
+ });
64339
66196
  register("__inferred_type_str", (_ctx, args) => {
64340
66197
  if (args.length !== 1) return FALL_THROUGH;
64341
66198
  const rv = ensureRuntimeValue(args[0]);
@@ -64360,6 +66217,44 @@ register("nargout", (ctx, args) => {
64360
66217
  const v = ctx.env.get("$nargout");
64361
66218
  return v !== void 0 ? v : 0;
64362
66219
  });
66220
+ register("inputname", (ctx, args) => {
66221
+ if (args.length !== 1) return FALL_THROUGH;
66222
+ const narginVal = ctx.env.get("$nargin");
66223
+ if (typeof narginVal !== "number") {
66224
+ throw new RuntimeError(
66225
+ "You can only call 'inputname' from within a MATLAB function."
66226
+ );
66227
+ }
66228
+ const k = toNumber(ensureRuntimeValue(args[0]));
66229
+ if (!Number.isInteger(k) || k < 1) {
66230
+ throw new RuntimeError(
66231
+ "Argument number must be a positive integer scalar."
66232
+ );
66233
+ }
66234
+ if (k > narginVal) {
66235
+ throw new RuntimeError(
66236
+ "Argument number exceeds number of function input arguments."
66237
+ );
66238
+ }
66239
+ const names = ctx.env.inputArgNames;
66240
+ const name = names && k <= names.length ? names[k - 1] : "";
66241
+ return RTV.char(name);
66242
+ });
66243
+ register("properties", (ctx, args) => {
66244
+ if (args.length !== 1) return FALL_THROUGH;
66245
+ const v = ensureRuntimeValue(args[0]);
66246
+ let names;
66247
+ if (isRuntimeClassInstance(v) || isRuntimeClassInstanceArray(v)) {
66248
+ names = ctx.classPublicProperties(v.className);
66249
+ } else if (isRuntimeChar(v) || isRuntimeString(v)) {
66250
+ names = ctx.classPublicProperties(toString(v));
66251
+ }
66252
+ if (!names) return RTV.cell([], [0, 1]);
66253
+ return RTV.cell(
66254
+ names.map((n) => RTV.string(n)),
66255
+ [names.length, 1]
66256
+ );
66257
+ });
64363
66258
  register("narginchk", (ctx, args) => {
64364
66259
  if (args.length !== 2) return FALL_THROUGH;
64365
66260
  const narginVal = ctx.env.get("$nargin");
@@ -64464,10 +66359,14 @@ function callFunction(name, args, nargout) {
64464
66359
  source: ""
64465
66360
  };
64466
66361
  return void 0;
64467
- }
66362
+ },
66363
+ classPublicProperties: (n) => classPublicProperties(this, n)
64468
66364
  };
64469
66365
  const result = specialHandler(ctx, args, nargout);
64470
- if (result !== FALL_THROUGH) return result;
66366
+ if (result !== FALL_THROUGH) {
66367
+ this.pendingInputNames = void 0;
66368
+ return result;
66369
+ }
64471
66370
  }
64472
66371
  const nested = this.env.getNestedFunction(name);
64473
66372
  if (nested) {
@@ -64487,6 +66386,75 @@ function callFunction(name, args, nargout) {
64487
66386
  }
64488
66387
  throw new RuntimeError(`Undefined function or variable '${name}'`);
64489
66388
  }
66389
+ function narginFromParams(params) {
66390
+ const hasVarargin = params.length > 0 && params[params.length - 1] === "varargin";
66391
+ return hasVarargin ? -params.length : params.length;
66392
+ }
66393
+ function paramsInFile(interp, fileName, funcName) {
66394
+ const ast = interp.ctx.getCachedAST(fileName);
66395
+ for (const stmt of ast.body) {
66396
+ if (stmt.type === "Function" && stmt.name === funcName) return stmt.params;
66397
+ }
66398
+ return void 0;
66399
+ }
66400
+ function declaredNargin(name) {
66401
+ const nested = this.env.getNestedFunction(name);
66402
+ if (nested) return narginFromParams(nested.fn.params);
66403
+ const callSite = {
66404
+ file: this.currentFile,
66405
+ ...this.currentClassName ? { className: this.currentClassName } : {},
66406
+ ...this.currentMethodName ? { methodName: this.currentMethodName } : {}
66407
+ };
66408
+ const target = resolveFunction(name, [], callSite, this.functionIndex);
66409
+ if (!target) return void 0;
66410
+ let params;
66411
+ switch (target.kind) {
66412
+ case "localFunction": {
66413
+ const { source } = target;
66414
+ if (source.from === "main") {
66415
+ params = this.mainLocalFunctions.get(target.name)?.params;
66416
+ } else if (source.from === "workspaceFile") {
66417
+ params = this.findFunctionInWorkspaceFile(source.wsName, target.name)?.params ?? void 0;
66418
+ } else if (source.from === "classFile") {
66419
+ params = this.findFunctionInClassFile(
66420
+ source.className,
66421
+ target.name,
66422
+ source.methodScope
66423
+ )?.params ?? void 0;
66424
+ } else if (source.from === "privateFile") {
66425
+ params = paramsInFile(this, source.callerFile, target.name);
66426
+ }
66427
+ break;
66428
+ }
66429
+ case "workspaceFunction": {
66430
+ const dotIdx = target.name.lastIndexOf(".");
66431
+ const primaryName = dotIdx >= 0 ? target.name.slice(dotIdx + 1) : target.name;
66432
+ params = this.findFunctionInWorkspaceFile(target.name, primaryName)?.params ?? void 0;
66433
+ if (params === void 0) {
66434
+ const entry = this.ctx.registry.filesByFuncName.get(target.name);
66435
+ if (entry) {
66436
+ const ast = this.ctx.getCachedAST(entry.fileName);
66437
+ for (const stmt of ast.body) {
66438
+ if (stmt.type === "Function") {
66439
+ params = stmt.params;
66440
+ break;
66441
+ }
66442
+ }
66443
+ }
66444
+ }
66445
+ break;
66446
+ }
66447
+ case "privateFunction": {
66448
+ const entry = this.ctx.getPrivateFileEntry(
66449
+ target.callerFile,
66450
+ target.name
66451
+ );
66452
+ if (entry) params = paramsInFile(this, entry.fileName, target.name);
66453
+ break;
66454
+ }
66455
+ }
66456
+ return params !== void 0 ? narginFromParams(params) : void 0;
66457
+ }
64490
66458
  function interpretTarget(target, args, nargout) {
64491
66459
  switch (target.kind) {
64492
66460
  case "builtin": {
@@ -64655,18 +66623,12 @@ function interpretWorkspaceFunction(target, args, nargout) {
64655
66623
  if (entry) {
64656
66624
  const ast = this.ctx.getCachedAST(entry.fileName);
64657
66625
  return this.withFileContext(entry.fileName, void 0, void 0, () => {
64658
- const savedEnv = this.env;
64659
- this.env = new Environment();
64660
- try {
64661
- for (const stmt of ast.body) {
64662
- if (stmt.type === "Function") continue;
64663
- const signal = this.execStmt(stmt);
64664
- if (signal instanceof ReturnSignal) break;
64665
- }
64666
- return this.ans;
64667
- } finally {
64668
- this.env = savedEnv;
66626
+ for (const stmt of ast.body) {
66627
+ if (stmt.type === "Function") continue;
66628
+ const signal = this.execStmt(stmt);
66629
+ if (signal instanceof ReturnSignal) break;
64669
66630
  }
66631
+ return this.ans;
64670
66632
  });
64671
66633
  }
64672
66634
  throw new RuntimeError(`Workspace function '${target.name}' not found`);
@@ -64733,6 +66695,21 @@ function instantiateClass(className, args, nargout) {
64733
66695
  if (!classInfo) {
64734
66696
  return this.rt.callClassMethod(className, className, nargout, args);
64735
66697
  }
66698
+ if (classInfo.isOldStyle) {
66699
+ const ctorName = classInfo.constructorName ?? className;
66700
+ const ctorFn = this.findExternalMethod(classInfo, ctorName);
66701
+ if (!ctorFn)
66702
+ throw new RuntimeError(
66703
+ `Constructor for old-style class '${className}' not found`
66704
+ );
66705
+ const fileName = classInfo.externalMethodFiles.get(ctorName)?.fileName ?? classInfo.fileName;
66706
+ return this.withFileContext(
66707
+ fileName,
66708
+ className,
66709
+ ctorName,
66710
+ () => this.callUserFunction(ctorFn, args, nargout)
66711
+ );
66712
+ }
64736
66713
  const { propertyNames, propertyDefaults } = this.collectClassProperties(classInfo);
64737
66714
  const defaults = /* @__PURE__ */ new Map();
64738
66715
  for (const [propName, defaultExpr] of propertyDefaults) {
@@ -64756,7 +66733,7 @@ function instantiateClass(className, args, nargout) {
64756
66733
  function interpretConstructor(classInfo, args, nargout) {
64757
66734
  const constructorName = classInfo.constructorName;
64758
66735
  if (!constructorName) return args[0];
64759
- for (const member of classInfo.ast.members) {
66736
+ for (const member of classInfo.ast?.members ?? []) {
64760
66737
  if (member.type !== "Methods") continue;
64761
66738
  for (const methodStmt of member.body) {
64762
66739
  if (methodStmt.type === "Function" && methodStmt.name === constructorName) {
@@ -64798,6 +66775,8 @@ function callUserFunction(fn, args, nargout, narginOverride) {
64798
66775
  if (!hasVarargoutDecl && nargout > declaredRegularOutputs) {
64799
66776
  throw new RuntimeError("Too many output arguments.");
64800
66777
  }
66778
+ const callInputNames = this.pendingInputNames;
66779
+ this.pendingInputNames = void 0;
64801
66780
  const sharedArgs = args;
64802
66781
  if (narginOverride === void 0 && this.registry.size > 0) {
64803
66782
  const r = this.registry.dispatchCall(fn, sharedArgs, nargout, this);
@@ -64831,6 +66810,7 @@ function callUserFunction(fn, args, nargout, narginOverride) {
64831
66810
  }
64832
66811
  fnEnv.set("$nargin", narginOverride ?? args.length);
64833
66812
  fnEnv.set("$nargout", nargout);
66813
+ fnEnv.inputArgNames = callInputNames;
64834
66814
  for (const stmt of fn.body) {
64835
66815
  if (stmt.type === "Function") {
64836
66816
  fnEnv.nestedFunctions.set(stmt.name, {
@@ -64907,10 +66887,13 @@ function callUserFunction(fn, args, nargout, narginOverride) {
64907
66887
  }
64908
66888
  }
64909
66889
  function callNestedFunction(fn, parentEnv, args, nargout) {
66890
+ const callInputNames = this.pendingInputNames;
66891
+ this.pendingInputNames = void 0;
64910
66892
  const fnEnv = new Environment(parentEnv);
64911
66893
  fnEnv.isNested = true;
64912
66894
  fnEnv.rt = this.rt;
64913
66895
  fnEnv.persistentFuncId = `${this.currentFile}:${fn.name}`;
66896
+ fnEnv.nestedLocalNames = /* @__PURE__ */ new Set([...fn.params, ...fn.outputs]);
64914
66897
  const hasVarargin = fn.params.length > 0 && fn.params[fn.params.length - 1] === "varargin";
64915
66898
  const regularParams = hasVarargin ? fn.params.slice(0, -1) : fn.params;
64916
66899
  for (let i = 0; i < regularParams.length; i++) {
@@ -64924,6 +66907,15 @@ function callNestedFunction(fn, parentEnv, args, nargout) {
64924
66907
  }
64925
66908
  fnEnv.setLocal("$nargin", args.length);
64926
66909
  fnEnv.setLocal("$nargout", nargout);
66910
+ fnEnv.inputArgNames = callInputNames;
66911
+ for (const stmt of fn.body) {
66912
+ if (stmt.type === "Function") {
66913
+ fnEnv.nestedFunctions.set(stmt.name, {
66914
+ fn: funcDefFromStmt(stmt),
66915
+ env: fnEnv
66916
+ });
66917
+ }
66918
+ }
64927
66919
  const savedEnv = this.env;
64928
66920
  this.env = fnEnv;
64929
66921
  this.rt.pushCleanupScope();
@@ -65062,7 +67054,7 @@ function findMethodInClass(classInfo, methodName) {
65062
67054
  const cacheKey = `method:${classInfo.name}:${methodName}`;
65063
67055
  const cached = this.functionDefCache.get(cacheKey);
65064
67056
  if (cached) return cached;
65065
- for (const member of classInfo.ast.members) {
67057
+ for (const member of classInfo.ast?.members ?? []) {
65066
67058
  if (member.type !== "Methods") continue;
65067
67059
  for (const methodStmt of member.body) {
65068
67060
  if (methodStmt.type === "Function" && methodStmt.name === methodName) {
@@ -65119,6 +67111,36 @@ function collectClassProperties(classInfo) {
65119
67111
  }
65120
67112
  return { propertyNames, propertyDefaults };
65121
67113
  }
67114
+ function classPublicProperties(interp, className) {
67115
+ let info = interp.ctx.getClassInfo(className);
67116
+ if (!info) return void 0;
67117
+ const out = [];
67118
+ const seen = /* @__PURE__ */ new Set();
67119
+ while (info) {
67120
+ const ast = info.ast;
67121
+ if (ast) {
67122
+ for (const member of ast.members) {
67123
+ if (member.type !== "Properties") continue;
67124
+ const hidden = member.attributes.some((a) => {
67125
+ const n = a.name.toLowerCase();
67126
+ if (n !== "access" && n !== "getaccess") return false;
67127
+ const val = (a.value ?? "").toLowerCase();
67128
+ return val === "private" || val === "protected";
67129
+ });
67130
+ if (hidden) continue;
67131
+ for (const pn of member.names) {
67132
+ if (!seen.has(pn)) {
67133
+ seen.add(pn);
67134
+ out.push(pn);
67135
+ }
67136
+ }
67137
+ }
67138
+ }
67139
+ if (!info.superClass || info.superClass === "handle") break;
67140
+ info = interp.ctx.getClassInfo(info.superClass);
67141
+ }
67142
+ return out;
67143
+ }
65122
67144
  function isHandleClass(classInfo) {
65123
67145
  let parentName = classInfo.superClass;
65124
67146
  while (parentName) {
@@ -65237,6 +67259,10 @@ var Interpreter = class {
65237
67259
  workspaceEnv;
65238
67260
  /** @internal The caller's environment — for evalin/assignin('caller', ...) */
65239
67261
  callerEnv;
67262
+ /** @internal Call-site variable names for the next user-function call, set
67263
+ * by `evalFuncCall` and consumed by `callUserFunction` to support
67264
+ * `inputname`. One-shot: cleared as soon as it is consumed. */
67265
+ pendingInputNames;
65240
67266
  /** @internal Stack of [base, dimIndex, numIndices] for resolving `end` keyword in indexing. */
65241
67267
  endContextStack = [];
65242
67268
  /** @internal Number of enclosing `for` / `while` loop bodies the
@@ -65498,6 +67524,37 @@ function extractClassInfo(classDef, qualifiedName, fileName, source) {
65498
67524
  externalMethodFiles: /* @__PURE__ */ new Map()
65499
67525
  };
65500
67526
  }
67527
+ function makeOldStyleClassInfo(qualifiedName, baseName, constructorFile, methodFiles) {
67528
+ const externalMethodFiles = /* @__PURE__ */ new Map();
67529
+ externalMethodFiles.set(baseName, {
67530
+ fileName: constructorFile.fileName,
67531
+ source: constructorFile.source
67532
+ });
67533
+ const methodNames = /* @__PURE__ */ new Set();
67534
+ for (const mf of methodFiles) {
67535
+ externalMethodFiles.set(mf.name, {
67536
+ fileName: mf.fileName,
67537
+ source: mf.source
67538
+ });
67539
+ methodNames.add(mf.name);
67540
+ }
67541
+ return {
67542
+ name: baseName,
67543
+ qualifiedName,
67544
+ fileName: constructorFile.fileName,
67545
+ source: constructorFile.source,
67546
+ superClass: null,
67547
+ propertyNames: [],
67548
+ propertyDefaults: /* @__PURE__ */ new Map(),
67549
+ methodNames,
67550
+ staticMethodNames: /* @__PURE__ */ new Set(),
67551
+ constructorName: baseName,
67552
+ inferiorClasses: [],
67553
+ ast: null,
67554
+ isOldStyle: true,
67555
+ externalMethodFiles
67556
+ };
67557
+ }
65501
67558
 
65502
67559
  // src/numbl-core/lowering/loweringContext.ts
65503
67560
  function createWorkspaceRegistry() {
@@ -65690,8 +67747,41 @@ var LoweringContext = class _LoweringContext {
65690
67747
  }
65691
67748
  }
65692
67749
  }
67750
+ const startsWithClassdef = (source) => {
67751
+ let t = source.trimStart();
67752
+ while (t.startsWith("%")) {
67753
+ const nl = t.indexOf("\n");
67754
+ if (nl < 0) return false;
67755
+ t = t.slice(nl + 1).trimStart();
67756
+ }
67757
+ return t.startsWith("classdef");
67758
+ };
65693
67759
  for (const [className, group] of classFolderGroups) {
65694
- if (!group.classDefFile) {
67760
+ const hasRealClassdef = !!group.classDefFile && startsWithClassdef(group.classDefFile.source);
67761
+ if (!hasRealClassdef) {
67762
+ const dotIdx = className.lastIndexOf(".");
67763
+ const baseName = dotIdx >= 0 ? className.slice(dotIdx + 1) : className;
67764
+ const methodBase = (f) => f.name.split("/").pop().replace(/\.m$/, "");
67765
+ const ctorFile = group.classDefFile ?? group.methodFiles.find((f) => methodBase(f) === baseName);
67766
+ if (!ctorFile) {
67767
+ continue;
67768
+ }
67769
+ const methodFiles = group.methodFiles.filter((f) => f !== ctorFile).map((f) => ({
67770
+ name: methodBase(f),
67771
+ fileName: f.name,
67772
+ source: f.source
67773
+ }));
67774
+ if (!this.registry.classesByName.has(className)) {
67775
+ this.registry.classesByName.set(
67776
+ className,
67777
+ makeOldStyleClassInfo(
67778
+ className,
67779
+ baseName,
67780
+ { fileName: ctorFile.name, source: ctorFile.source },
67781
+ methodFiles
67782
+ )
67783
+ );
67784
+ }
65695
67785
  continue;
65696
67786
  }
65697
67787
  this.registerWorkspaceClass(className, group.classDefFile);
@@ -65927,7 +68017,7 @@ var LoweringContext = class _LoweringContext {
65927
68017
  if (!info) return null;
65928
68018
  if (info.ctx) return info.ctx;
65929
68019
  const ctx = new _LoweringContext(info.source, info.fileName);
65930
- for (const member of info.ast.members) {
68020
+ for (const member of info.ast?.members ?? []) {
65931
68021
  if (member.type !== "Methods") continue;
65932
68022
  for (const methodStmt of member.body) {
65933
68023
  if (methodStmt.type !== "Function") continue;
@@ -66086,6 +68176,7 @@ var LoweringContext = class _LoweringContext {
66086
68176
  for (const m of info.methodNames) instanceMethods.add(m);
66087
68177
  for (const m of info.staticMethodNames) staticMethods.add(m);
66088
68178
  for (const m of info.externalMethodFiles.keys()) {
68179
+ if (info.isOldStyle && m === info.constructorName) continue;
66089
68180
  if (!info.staticMethodNames.has(m)) {
66090
68181
  instanceMethods.add(m);
66091
68182
  }
@@ -66129,10 +68220,43 @@ var LoweringContext = class _LoweringContext {
66129
68220
  }
66130
68221
  }
66131
68222
  const fileImports = /* @__PURE__ */ new Map();
68223
+ const gatherImports = (body, out) => {
68224
+ for (const stmt of body) {
68225
+ switch (stmt.type) {
68226
+ case "Import":
68227
+ out.push(stmt);
68228
+ break;
68229
+ case "Function":
68230
+ case "While":
68231
+ case "For":
68232
+ gatherImports(stmt.body, out);
68233
+ break;
68234
+ case "If":
68235
+ gatherImports(stmt.thenBody, out);
68236
+ for (const b of stmt.elseifBlocks) gatherImports(b.body, out);
68237
+ if (stmt.elseBody) gatherImports(stmt.elseBody, out);
68238
+ break;
68239
+ case "Switch":
68240
+ for (const c of stmt.cases) gatherImports(c.body, out);
68241
+ if (stmt.otherwise) gatherImports(stmt.otherwise, out);
68242
+ break;
68243
+ case "TryCatch":
68244
+ gatherImports(stmt.tryBody, out);
68245
+ gatherImports(stmt.catchBody, out);
68246
+ break;
68247
+ case "ClassDef":
68248
+ for (const member of stmt.members) {
68249
+ if (member.type === "Methods") gatherImports(member.body, out);
68250
+ }
68251
+ break;
68252
+ }
68253
+ }
68254
+ };
66132
68255
  const collectImportsFromBody = (body, fileName) => {
66133
68256
  const entries = [];
66134
- for (const stmt of body) {
66135
- if (stmt.type !== "Import") continue;
68257
+ const importStmts = [];
68258
+ gatherImports(body, importStmts);
68259
+ for (const stmt of importStmts) {
66136
68260
  if (stmt.wildcard) {
66137
68261
  entries.push({ wildcard: true, namespace: stmt.path.join(".") });
66138
68262
  } else {
@@ -66207,6 +68331,10 @@ var LoweringContext = class _LoweringContext {
66207
68331
 
66208
68332
  // src/numbl-core/stdlib-bundle.ts
66209
68333
  var stdlibFiles = [
68334
+ {
68335
+ name: "TriRep.m",
68336
+ 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"
68337
+ },
66210
68338
  {
66211
68339
  name: "addOptional.m",
66212
68340
  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"
@@ -66234,6 +68362,10 @@ var stdlibFiles = [
66234
68362
  {
66235
68363
  name: "readmatrix.m",
66236
68364
  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"
68365
+ },
68366
+ {
68367
+ name: "triangulation.m",
68368
+ 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"
66237
68369
  }
66238
68370
  ];
66239
68371
  var shimFiles = [
@@ -91810,6 +93942,7 @@ var Workspace = class _Workspace {
91810
93942
  }
91811
93943
  };
91812
93944
  for (const [name, info] of this.ctx.registry.classesByName) {
93945
+ if (info.isOldStyle) continue;
91813
93946
  registerOrDefer(
91814
93947
  name,
91815
93948
  () => registerClassDef(
@@ -91823,7 +93956,7 @@ var Workspace = class _Workspace {
91823
93956
  if (this.classes.has(name) || this.failedClassValidations.has(name)) {
91824
93957
  throw new UnsupportedConstruct(
91825
93958
  `class '${name}' is defined both locally and as a workspace class`,
91826
- info.ast.span
93959
+ info.ast?.span
91827
93960
  );
91828
93961
  }
91829
93962
  registerOrDefer(name, () => registerClassDef(info.ast, info.fileName));
@@ -91917,7 +94050,7 @@ var Workspace = class _Workspace {
91917
94050
  if (!ast) {
91918
94051
  throw new UnsupportedConstruct(
91919
94052
  `internal: external method file '${mf.fileName}' for '${info.qualifiedName}.${methodName}' was not parsed`,
91920
- info.ast.span
94053
+ info.ast?.span
91921
94054
  );
91922
94055
  }
91923
94056
  let primary = null;
@@ -91933,7 +94066,7 @@ var Workspace = class _Workspace {
91933
94066
  if (!primary) {
91934
94067
  throw new UnsupportedConstruct(
91935
94068
  `external method file '${mf.fileName}' has no function`,
91936
- info.ast.span
94069
+ info.ast?.span
91937
94070
  );
91938
94071
  }
91939
94072
  out.set(methodName, primary);
@@ -96365,6 +98498,7 @@ async function load() {
96365
98498
  const { loadQhull } = await import("qhull-wasm");
96366
98499
  const qhull = await loadQhull();
96367
98500
  setDelaunayBackend((points, dim) => qhull.delaunay(points, dim).facets);
98501
+ setConvexHullBackend((points, dim) => qhull.convexHull(points, dim).facets);
96368
98502
  }
96369
98503
 
96370
98504
  // src/cli.ts
@@ -96504,6 +98638,7 @@ function findTestFiles(dir) {
96504
98638
  }
96505
98639
  async function runTests(dir, optimization, fastMath) {
96506
98640
  loadNativeAddon(fastMath ?? false);
98641
+ await loadQhullBackend();
96507
98642
  const absDir = resolve2(process.cwd(), dir);
96508
98643
  const testFiles = findTestFiles(absDir);
96509
98644
  if (testFiles.length === 0) {
@@ -96599,6 +98734,8 @@ Options (for build-site):
96599
98734
  --title <text> Site title (default: from numbl-project.json or dir name)
96600
98735
  --entry <file> Default file to open, e.g. main.m (default: README.md or
96601
98736
  the first script)
98737
+ --repo-url <url> Source repository URL, shown as a link in the deployed
98738
+ site (default: from numbl-project.json)
96602
98739
 
96603
98740
  Options (for parse):
96604
98741
  --dump-ast <file> Write the AST as indented JSON to <file> (default: stdout)
@@ -97196,6 +99333,7 @@ async function cmdBuildSite(args) {
97196
99333
  let base;
97197
99334
  let title;
97198
99335
  let entry;
99336
+ let repoUrl;
97199
99337
  const positional = [];
97200
99338
  for (let i = 0; i < args.length; i++) {
97201
99339
  const a = args[i];
@@ -97203,6 +99341,7 @@ async function cmdBuildSite(args) {
97203
99341
  else if (a === "--base") base = args[++i];
97204
99342
  else if (a === "--title") title = args[++i];
97205
99343
  else if (a === "--entry") entry = args[++i];
99344
+ else if (a === "--repo-url") repoUrl = args[++i];
97206
99345
  else if (a.startsWith("-")) {
97207
99346
  console.error(`Unknown option: ${a}`);
97208
99347
  process.exit(1);
@@ -97255,6 +99394,7 @@ async function cmdBuildSite(args) {
97255
99394
  }
97256
99395
  if (title !== void 0) manifest.title = title;
97257
99396
  if (entry !== void 0) manifest.entry = entry;
99397
+ if (repoUrl !== void 0) manifest.repository = repoUrl;
97258
99398
  if (!manifest.title) manifest.title = basename2(projectDirAbs);
97259
99399
  const zipEntries = {};
97260
99400
  for (const f of collected) {
@@ -97275,14 +99415,17 @@ async function cmdBuildSite(args) {
97275
99415
  "warning: project bundle exceeds 50 MB; GitHub Pages may be slow or reject it"
97276
99416
  );
97277
99417
  }
99418
+ const buildId = createHash2("sha256").update(zip).digest("hex").slice(0, 16);
97278
99419
  const indexPath = join7(outDirAbs, "index.html");
99420
+ let injectJs = `window.__NUMBL_BUILD_ID__=${JSON.stringify(buildId)};`;
97279
99421
  if (normBase) {
97280
- let indexHtml = readFileSync6(indexPath, "utf-8");
97281
- const inject = `<script>window.__NUMBL_BASE__=${JSON.stringify(normBase)};</script>`;
97282
- indexHtml = indexHtml.includes("<!-- numbl:base -->") ? indexHtml.replace("<!-- numbl:base -->", inject) : indexHtml.replace("</head>", ` ${inject}
97283
- </head>`);
97284
- writeFileSync4(indexPath, indexHtml);
99422
+ injectJs += `window.__NUMBL_BASE__=${JSON.stringify(normBase)};`;
97285
99423
  }
99424
+ const inject = `<script>${injectJs}</script>`;
99425
+ let indexHtml = readFileSync6(indexPath, "utf-8");
99426
+ indexHtml = indexHtml.includes("<!-- numbl:base -->") ? indexHtml.replace("<!-- numbl:base -->", inject) : indexHtml.replace("</head>", ` ${inject}
99427
+ </head>`);
99428
+ writeFileSync4(indexPath, indexHtml);
97286
99429
  const redirectTarget = normBase ?? "/";
97287
99430
  writeFileSync4(
97288
99431
  join7(outDirAbs, "404.html"),