numbl 0.4.9 → 0.4.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist-lib/lib.js CHANGED
@@ -280,12 +280,17 @@ var RuntimeClassInstance = class extends Refcounted {
280
280
  /** For classes that inherit from built-in types (e.g. classdef Foo < double),
281
281
  * stores the underlying built-in data. */
282
282
  _builtinData;
283
- constructor(className, fields, isHandleClass2, _builtinData) {
283
+ /** For enumeration-class members: the member name (e.g. "tri"). When set,
284
+ * this instance is an enumeration member and `_builtinData` holds its
285
+ * underlying (superclass) value. Drives ==/~=, switch, and char coercion. */
286
+ _enumMember;
287
+ constructor(className, fields, isHandleClass2, _builtinData, _enumMember) {
284
288
  super();
285
289
  this.className = className;
286
290
  this.fields = fields;
287
291
  this.isHandleClass = isHandleClass2;
288
292
  this._builtinData = _builtinData;
293
+ this._enumMember = _enumMember;
289
294
  for (const v of fields.values()) incref(v);
290
295
  if (_builtinData !== void 0) incref(_builtinData);
291
296
  }
@@ -788,6 +793,10 @@ var valuesAreEqual = (a, b) => {
788
793
  return a === b;
789
794
  }
790
795
  case "class_instance": {
796
+ const bi = b;
797
+ if (a._enumMember !== void 0 || bi._enumMember !== void 0) {
798
+ return a._enumMember === bi._enumMember && a.className === bi.className;
799
+ }
791
800
  return a === b;
792
801
  }
793
802
  case "complex_number": {
@@ -21556,6 +21565,15 @@ function inferJitType(value) {
21556
21565
  fields
21557
21566
  };
21558
21567
  }
21568
+ if (isRuntimeClassInstanceArray(value)) {
21569
+ const arr = value;
21570
+ return {
21571
+ kind: "class_instance",
21572
+ className: arr.className,
21573
+ isHandleClass: false,
21574
+ fields: {}
21575
+ };
21576
+ }
21559
21577
  if (typeof value === "object" && value !== null && value.kind === "function") {
21560
21578
  return { kind: "function_handle" };
21561
21579
  }
@@ -24044,6 +24062,19 @@ function assignSlice(base, rowIndices, colIndices, rhs, curRows) {
24044
24062
  if (isRuntimeTensor(rhs)) {
24045
24063
  const nR = rowIndices.length;
24046
24064
  const nC = colIndices.length;
24065
+ if (rhs.data.length === 1) {
24066
+ const re = rhs.data[0];
24067
+ const im = rhs.imag ? rhs.imag[0] : 0;
24068
+ if (im !== 0 || base.imag) ensureImag(base);
24069
+ for (const r of rowIndices) {
24070
+ for (const c of colIndices) {
24071
+ const li = colMajorIndex(r, c, curRows);
24072
+ base.data[li] = re;
24073
+ if (base.imag) base.imag[li] = im;
24074
+ }
24075
+ }
24076
+ return;
24077
+ }
24047
24078
  const [rhsRows, rhsCols] = tensorSize2D(rhs);
24048
24079
  const shapeMatches = rhsRows === nR && rhsCols === nC;
24049
24080
  const sliceIsVector = nR === 1 || nC === 1;
@@ -31246,11 +31277,15 @@ var ClassParser = class extends FunctionParser {
31246
31277
  } else if (tok === 20 /* Enumeration */) {
31247
31278
  this.pos++;
31248
31279
  const attrs = this.parseOptionalAttrList();
31249
- const names = this.parseNameBlock();
31280
+ const enumMembers = this.parseEnumerationBlock();
31250
31281
  if (!this.consume(15 /* End */)) {
31251
31282
  throw this.error("expected 'end' after enumeration");
31252
31283
  }
31253
- members.push({ type: "Enumeration", attributes: attrs, names });
31284
+ members.push({
31285
+ type: "Enumeration",
31286
+ attributes: attrs,
31287
+ members: enumMembers
31288
+ });
31254
31289
  } else if (tok === 21 /* Arguments */) {
31255
31290
  this.pos++;
31256
31291
  const attrs = this.parseOptionalAttrList();
@@ -31291,6 +31326,38 @@ var ClassParser = class extends FunctionParser {
31291
31326
  }
31292
31327
  return names;
31293
31328
  }
31329
+ /** Parse an `enumeration ... end` member list. Each member is a bare
31330
+ * identifier optionally followed by a parenthesized argument list
31331
+ * (`quad (0)`, `Error (1, 0, 0)`). Members are separated by `;`/`,`/
31332
+ * newline. The args are evaluated later (at member construction). */
31333
+ parseEnumerationBlock() {
31334
+ const members = [];
31335
+ while (this.peekToken() !== void 0) {
31336
+ if (this.peekToken() === 15 /* End */) break;
31337
+ if (this.consume(49 /* Semicolon */) || this.consume(47 /* Comma */) || this.consume(68 /* Newline */)) {
31338
+ continue;
31339
+ }
31340
+ if (this.peekToken() !== 4 /* Ident */) break;
31341
+ const name = this.expectIdent();
31342
+ const args = [];
31343
+ if (this.consume(50 /* LParen */)) {
31344
+ if (this.peekToken() !== 51 /* RParen */) {
31345
+ args.push(this.parseExpr());
31346
+ while (this.consume(47 /* Comma */)) {
31347
+ args.push(this.parseExpr());
31348
+ }
31349
+ }
31350
+ if (!this.consume(51 /* RParen */)) {
31351
+ throw this.errorWithExpected(
31352
+ "expected ')' after enumeration member arguments",
31353
+ ")"
31354
+ );
31355
+ }
31356
+ }
31357
+ members.push({ name, args });
31358
+ }
31359
+ return members;
31360
+ }
31294
31361
  parsePropertiesNamesBlock() {
31295
31362
  const props = [];
31296
31363
  while (this.peekToken() !== void 0) {
@@ -32132,6 +32199,96 @@ function getConstant(name) {
32132
32199
  var COLON_SENTINEL = /* @__PURE__ */ Symbol("COLON");
32133
32200
  var END_SENTINEL = /* @__PURE__ */ Symbol("END");
32134
32201
 
32202
+ // src/numbl-core/runtime/runtimeEnum.ts
32203
+ function isEnumInstance(v) {
32204
+ return isRuntimeClassInstance(v) && v._enumMember !== void 0;
32205
+ }
32206
+ function isEnumValue(v) {
32207
+ if (isEnumInstance(v)) return true;
32208
+ if (isRuntimeClassInstanceArray(v))
32209
+ return v.elements.some((e) => e._enumMember !== void 0);
32210
+ return false;
32211
+ }
32212
+ function asText(v) {
32213
+ if (isRuntimeChar(v)) return v.value;
32214
+ if (isRuntimeString(v)) return v;
32215
+ return null;
32216
+ }
32217
+ function enumScalarEquals(e, other) {
32218
+ const text = asText(other);
32219
+ if (text !== null) return e._enumMember === text;
32220
+ if (isEnumInstance(other)) {
32221
+ if (e._builtinData !== void 0 && other._builtinData !== void 0) {
32222
+ return toNumber(e._builtinData) === toNumber(other._builtinData);
32223
+ }
32224
+ return e._enumMember === other._enumMember;
32225
+ }
32226
+ if (e._builtinData !== void 0) {
32227
+ if (isRuntimeNumber(other)) return toNumber(e._builtinData) === other;
32228
+ if (isRuntimeLogical(other))
32229
+ return toNumber(e._builtinData) === (other ? 1 : 0);
32230
+ }
32231
+ return false;
32232
+ }
32233
+ function decompose(v) {
32234
+ if (isEnumInstance(v)) return { elems: [v], shape: [1, 1] };
32235
+ if (isRuntimeClassInstanceArray(v))
32236
+ return { elems: [...v.elements], shape: v.shape };
32237
+ if (isRuntimeChar(v)) return { elems: [v], shape: [1, 1] };
32238
+ if (isRuntimeString(v)) return { elems: [v], shape: [1, 1] };
32239
+ if (isRuntimeStringArray(v))
32240
+ return { elems: v.data.map((s) => s), shape: v.shape };
32241
+ if (isRuntimeNumber(v) || isRuntimeLogical(v))
32242
+ return { elems: [v], shape: [1, 1] };
32243
+ if (isRuntimeTensor(v)) {
32244
+ const shape = [v.shape[0] ?? 0, v.shape[1] ?? 1];
32245
+ return { elems: Array.from(v.data, (x) => x), shape };
32246
+ }
32247
+ return null;
32248
+ }
32249
+ function elementEquals(x, y) {
32250
+ if (isEnumInstance(x)) return enumScalarEquals(x, y);
32251
+ if (isEnumInstance(y)) return enumScalarEquals(y, x);
32252
+ return false;
32253
+ }
32254
+ function enumEqualityOp(op, a, b) {
32255
+ if (op !== "Equal" /* Equal */ && op !== "NotEqual" /* NotEqual */)
32256
+ return null;
32257
+ if (!isEnumValue(a) && !isEnumValue(b)) return null;
32258
+ const oa = decompose(a);
32259
+ const ob = decompose(b);
32260
+ if (!oa || !ob) return null;
32261
+ const na = oa.elems.length;
32262
+ const nb = ob.elems.length;
32263
+ let n;
32264
+ let shape;
32265
+ if (na === nb) {
32266
+ n = na;
32267
+ shape = oa.shape;
32268
+ } else if (na === 1) {
32269
+ n = nb;
32270
+ shape = ob.shape;
32271
+ } else if (nb === 1) {
32272
+ n = na;
32273
+ shape = oa.shape;
32274
+ } else {
32275
+ throw new RuntimeError("Array dimensions must match for comparison");
32276
+ }
32277
+ const negate = op === "NotEqual" /* NotEqual */;
32278
+ const out = new Float64Array(n);
32279
+ for (let i = 0; i < n; i++) {
32280
+ const eq2 = elementEquals(
32281
+ oa.elems[na === 1 ? 0 : i],
32282
+ ob.elems[nb === 1 ? 0 : i]
32283
+ );
32284
+ out[i] = (negate ? !eq2 : eq2) ? 1 : 0;
32285
+ }
32286
+ if (n === 1) return RTV.logical(out[0] === 1);
32287
+ const t = RTV.tensor(out, [shape[0], shape[1]]);
32288
+ t._isLogical = true;
32289
+ return t;
32290
+ }
32291
+
32135
32292
  // src/numbl-core/runtime/runtimeHelpers.ts
32136
32293
  function isDeferredRange(v) {
32137
32294
  return typeof v === "object" && v !== null && v["_deferredRange"] === true;
@@ -32156,6 +32313,8 @@ function ensureRuntimeValue(v) {
32156
32313
  return v;
32157
32314
  }
32158
32315
  function switchValuesMatch(a, b) {
32316
+ if (isEnumInstance(a)) return enumScalarEquals(a, b);
32317
+ if (isEnumInstance(b)) return enumScalarEquals(b, a);
32159
32318
  if ((isRuntimeChar(a) || isRuntimeString(a)) && (isRuntimeChar(b) || isRuntimeString(b))) {
32160
32319
  return (typeof a === "string" ? a : a.value) === (typeof b === "string" ? b : b.value);
32161
32320
  }
@@ -34870,6 +35029,8 @@ function sparseToDense2(S) {
34870
35029
  return RTV.tensor(data, [S.m, S.n], imag2);
34871
35030
  }
34872
35031
  function valuesEqualSimple(a, b) {
35032
+ if (isEnumInstance(a)) return enumScalarEquals(a, b);
35033
+ if (isEnumInstance(b)) return enumScalarEquals(b, a);
34873
35034
  {
34874
35035
  const aText = textValue(a);
34875
35036
  const bText = textValue(b);
@@ -43412,7 +43573,8 @@ function copyClassInstance(inst) {
43412
43573
  inst.className,
43413
43574
  new Map(inst.fields),
43414
43575
  inst.isHandleClass,
43415
- inst._builtinData
43576
+ inst._builtinData,
43577
+ inst._enumMember
43416
43578
  );
43417
43579
  }
43418
43580
  function repelemObjects(v, repArgs) {
@@ -46500,6 +46662,15 @@ defineBuiltin({
46500
46662
  });
46501
46663
 
46502
46664
  // src/numbl-core/interpreter/builtins/type-constructors.ts
46665
+ function classInstanceArrayNumeric(v) {
46666
+ if (!isRuntimeClassInstanceArray(v)) return null;
46667
+ const data = allocFloat64Array(v.elements.length);
46668
+ for (let i = 0; i < v.elements.length; i++) {
46669
+ const bd = v.elements[i]._builtinData;
46670
+ data[i] = bd !== void 0 ? toNumber(bd) : NaN;
46671
+ }
46672
+ return RTV.tensor(data, [v.shape[0], v.shape[1]]);
46673
+ }
46503
46674
  function parseDoubleText(s) {
46504
46675
  const t = s.trim();
46505
46676
  if (t === "") return NaN;
@@ -46554,6 +46725,8 @@ defineBuiltin({
46554
46725
  if (isRuntimeClassInstance(v) && v._builtinData !== void 0) {
46555
46726
  return v._builtinData;
46556
46727
  }
46728
+ const arr = classInstanceArrayNumeric(v);
46729
+ if (arr) return arr;
46557
46730
  return RTV.num(toNumber(v));
46558
46731
  }
46559
46732
  }
@@ -46581,6 +46754,8 @@ function singleApply(v) {
46581
46754
  }
46582
46755
  if (isRuntimeClassInstance(v) && v._builtinData !== void 0)
46583
46756
  return singleApply(v._builtinData);
46757
+ const arr = classInstanceArrayNumeric(v);
46758
+ if (arr) return singleApply(arr);
46584
46759
  return RTV.num(Math.fround(toNumber(v)));
46585
46760
  }
46586
46761
  defineBuiltin({
@@ -46640,7 +46815,7 @@ for (const { name, min: min2, max: max2 } of INT_RANGES) {
46640
46815
  match: (argTypes) => {
46641
46816
  if (argTypes.length !== 1) return null;
46642
46817
  const a = argTypes[0];
46643
- if (a.kind === "number" || a.kind === "boolean" || a.kind === "char" || a.kind === "complex_or_number")
46818
+ if (a.kind === "number" || a.kind === "boolean" || a.kind === "char" || a.kind === "complex_or_number" || a.kind === "class_instance")
46644
46819
  return [{ kind: "number" }];
46645
46820
  if (a.kind === "tensor")
46646
46821
  return [
@@ -46654,7 +46829,7 @@ for (const { name, min: min2, max: max2 } of INT_RANGES) {
46654
46829
  return null;
46655
46830
  },
46656
46831
  apply: (args) => {
46657
- const v = args[0];
46832
+ const v = isRuntimeClassInstance(args[0]) && args[0]._builtinData !== void 0 ? args[0]._builtinData : classInstanceArrayNumeric(args[0]) ?? args[0];
46658
46833
  if (isRuntimeNumber(v))
46659
46834
  return RTV.num(saturateRoundToward(v, min2, max2));
46660
46835
  if (isRuntimeLogical(v)) return RTV.num(v ? 1 : 0);
@@ -46909,7 +47084,7 @@ defineBuiltin({
46909
47084
  match: (argTypes) => {
46910
47085
  if (argTypes.length !== 1) return null;
46911
47086
  const a = argTypes[0];
46912
- if (a.kind === "number" || a.kind === "boolean")
47087
+ if (a.kind === "number" || a.kind === "boolean" || a.kind === "class_instance")
46913
47088
  return [{ kind: "boolean" }];
46914
47089
  if (a.kind === "tensor")
46915
47090
  return [
@@ -46924,7 +47099,7 @@ defineBuiltin({
46924
47099
  return null;
46925
47100
  },
46926
47101
  apply: (args) => {
46927
- const v = args[0];
47102
+ const v = isRuntimeClassInstance(args[0]) && args[0]._builtinData !== void 0 ? args[0]._builtinData : classInstanceArrayNumeric(args[0]) ?? args[0];
46928
47103
  if (isRuntimeTensor(v)) {
46929
47104
  const result = allocFloat64Array(v.data.length);
46930
47105
  for (let i = 0; i < v.data.length; i++) {
@@ -59928,7 +60103,16 @@ function indexStore(rt, base, indices, rhs, skipSubsasgn = false) {
59928
60103
  }
59929
60104
  existing[k] = rhsInst;
59930
60105
  if (existing.length === 1) return existing[0];
59931
- return RTV.classInstanceArray(className, existing, [1, existing.length]);
60106
+ const origShape = isRuntimeClassInstanceArray(mv) ? mv.shape : null;
60107
+ let shape;
60108
+ if (origShape && existing.length === origShape[0] * origShape[1]) {
60109
+ shape = origShape;
60110
+ } else if (origShape && origShape[1] === 1) {
60111
+ shape = [existing.length, 1];
60112
+ } else {
60113
+ shape = [1, existing.length];
60114
+ }
60115
+ return RTV.classInstanceArray(className, existing, shape);
59932
60116
  }
59933
60117
  }
59934
60118
  if (isRuntimeClassInstance(mv)) {
@@ -65588,6 +65772,8 @@ function evalBinary(expr) {
65588
65772
  const right = this.evalExpr(expr.right);
65589
65773
  const lv = ensureRuntimeValue(left);
65590
65774
  const rv = ensureRuntimeValue(right);
65775
+ const enumResult = enumEqualityOp(expr.op, lv, rv);
65776
+ if (enumResult !== null) return enumResult;
65591
65777
  if (isRuntimeClassInstance(lv) || isRuntimeClassInstance(rv)) {
65592
65778
  return this.rt.binop(expr.op, left, right);
65593
65779
  }
@@ -65692,6 +65878,8 @@ function evalMember(expr, nargout) {
65692
65878
  if (lastDot > 0) {
65693
65879
  const prefix = dottedName.slice(0, lastDot);
65694
65880
  const methodName = dottedName.slice(lastDot + 1);
65881
+ const enumMember = this.interpretEnumMember(prefix, methodName);
65882
+ if (enumMember !== null) return enumMember;
65695
65883
  if (this.functionIndex.classStaticMethods.get(prefix)?.has(methodName) || // Implicit static `ClassName.empty` (handled by interpretClassMethod)
65696
65884
  methodName === "empty" && this.functionIndex.workspaceClasses.has(prefix)) {
65697
65885
  const target = {
@@ -66151,6 +66339,7 @@ __export(interpreterFunctions_exports, {
66151
66339
  callNestedFunction: () => callNestedFunction,
66152
66340
  callUserFunction: () => callUserFunction,
66153
66341
  collectClassProperties: () => collectClassProperties,
66342
+ convertToEnum: () => convertToEnum,
66154
66343
  declaredNargin: () => declaredNargin,
66155
66344
  evalInLocalScope: () => evalInLocalScope,
66156
66345
  findExternalMethod: () => findExternalMethod,
@@ -66162,6 +66351,7 @@ __export(interpreterFunctions_exports, {
66162
66351
  instantiateClass: () => instantiateClass,
66163
66352
  interpretClassMethod: () => interpretClassMethod,
66164
66353
  interpretConstructor: () => interpretConstructor,
66354
+ interpretEnumMember: () => interpretEnumMember,
66165
66355
  interpretJsUserFunction: () => interpretJsUserFunction,
66166
66356
  interpretLocalFunction: () => interpretLocalFunction,
66167
66357
  interpretPrivateFunction: () => interpretPrivateFunction,
@@ -66991,11 +67181,95 @@ function interpretPrivateFunction(target, args, nargout) {
66991
67181
  }
66992
67182
  throw new RuntimeError(`Private function '${target.name}' not found`);
66993
67183
  }
67184
+ var NUMERIC_ENUM_SUPERS = /* @__PURE__ */ new Set([
67185
+ "double",
67186
+ "single",
67187
+ "int8",
67188
+ "int16",
67189
+ "int32",
67190
+ "int64",
67191
+ "uint8",
67192
+ "uint16",
67193
+ "uint32",
67194
+ "uint64",
67195
+ "logical",
67196
+ "char"
67197
+ ]);
67198
+ function enumUnderlyingValue(interp, classInfo, argExprs) {
67199
+ if (argExprs.length === 0) return void 0;
67200
+ const first = ensureRuntimeValue(interp.evalExpr(argExprs[0]));
67201
+ const sc = classInfo.superClass;
67202
+ if (sc && NUMERIC_ENUM_SUPERS.has(sc)) {
67203
+ return ensureRuntimeValue(interp.rt.callBuiltin(sc, 1, [first]));
67204
+ }
67205
+ return first;
67206
+ }
67207
+ function interpretEnumMember(className, memberName) {
67208
+ const classInfo = this.ctx.getClassInfo(className);
67209
+ if (!classInfo || !classInfo.enumMembers) return null;
67210
+ const memberDef = classInfo.enumMembers.find((m) => m.name === memberName);
67211
+ if (!memberDef) return null;
67212
+ const value = enumUnderlyingValue(this, classInfo, memberDef.args);
67213
+ return new RuntimeClassInstance(
67214
+ className,
67215
+ /* @__PURE__ */ new Map(),
67216
+ false,
67217
+ value,
67218
+ memberName
67219
+ );
67220
+ }
67221
+ function convertToEnum(className, classInfo, args) {
67222
+ if (args.length === 0) {
67223
+ throw new RuntimeError(
67224
+ `${className}: enumeration requires an argument to construct`
67225
+ );
67226
+ }
67227
+ const val = ensureRuntimeValue(args[0]);
67228
+ if (isRuntimeClassInstance(val) && val._enumMember !== void 0) return val;
67229
+ if (isRuntimeClassInstanceArray(val)) return val;
67230
+ const members = classInfo.enumMembers.map((m) => ({
67231
+ name: m.name,
67232
+ value: enumUnderlyingValue(this, classInfo, m.args)
67233
+ }));
67234
+ const makeMember = (name, value) => new RuntimeClassInstance(className, /* @__PURE__ */ new Map(), false, value, name);
67235
+ const fromName = (text) => {
67236
+ const m = members.find((mm) => mm.name === text);
67237
+ if (!m)
67238
+ throw new RuntimeError(
67239
+ `'${text}' is not a valid member of enumeration '${className}'`
67240
+ );
67241
+ return makeMember(m.name, m.value);
67242
+ };
67243
+ const fromNumber = (num) => {
67244
+ const m = members.find(
67245
+ (mm) => mm.value !== void 0 && toNumber(mm.value) === num
67246
+ );
67247
+ if (!m)
67248
+ throw new RuntimeError(
67249
+ `${num} is not a valid underlying value for enumeration '${className}'`
67250
+ );
67251
+ return makeMember(m.name, m.value);
67252
+ };
67253
+ if (isRuntimeChar(val)) return fromName(val.value);
67254
+ if (isRuntimeString(val)) return fromName(val);
67255
+ if (isRuntimeNumber(val)) return fromNumber(val);
67256
+ if (isRuntimeLogical(val)) return fromNumber(val ? 1 : 0);
67257
+ if (isRuntimeTensor(val)) {
67258
+ const elems = Array.from(val.data, (x) => fromNumber(x));
67259
+ if (elems.length === 1) return elems[0];
67260
+ const shape = [val.shape[0] ?? 0, val.shape[1] ?? 1];
67261
+ return RTV.classInstanceArray(className, elems, shape);
67262
+ }
67263
+ throw new RuntimeError(`Cannot convert value to enumeration '${className}'`);
67264
+ }
66994
67265
  function instantiateClass(className, args, nargout) {
66995
67266
  const classInfo = this.ctx.getClassInfo(className);
66996
67267
  if (!classInfo) {
66997
67268
  return this.rt.callClassMethod(className, className, nargout, args);
66998
67269
  }
67270
+ if (classInfo.enumMembers && args.length > 0) {
67271
+ return this.convertToEnum(className, classInfo, args);
67272
+ }
66999
67273
  if (classInfo.isOldStyle) {
67000
67274
  const ctorName = classInfo.constructorName ?? className;
67001
67275
  const ctorFn = this.findExternalMethod(classInfo, ctorName);
@@ -67796,6 +68070,7 @@ function extractClassInfo(classDef, qualifiedName, fileName, source) {
67796
68070
  const methodNames = /* @__PURE__ */ new Set();
67797
68071
  const staticMethodNames = /* @__PURE__ */ new Set();
67798
68072
  let constructorName = null;
68073
+ let enumMembers = null;
67799
68074
  const dotIdx = qualifiedName.lastIndexOf(".");
67800
68075
  const baseName = dotIdx >= 0 ? qualifiedName.slice(dotIdx + 1) : qualifiedName;
67801
68076
  for (const member of classDef.members) {
@@ -67837,6 +68112,8 @@ function extractClassInfo(classDef, qualifiedName, fileName, source) {
67837
68112
  }
67838
68113
  }
67839
68114
  }
68115
+ } else if (member.type === "Enumeration") {
68116
+ enumMembers = member.members.map((m) => ({ name: m.name, args: m.args }));
67840
68117
  }
67841
68118
  }
67842
68119
  const inferiorClasses = [];
@@ -67859,6 +68136,7 @@ function extractClassInfo(classDef, qualifiedName, fileName, source) {
67859
68136
  methodNames,
67860
68137
  staticMethodNames,
67861
68138
  constructorName,
68139
+ enumMembers,
67862
68140
  inferiorClasses,
67863
68141
  ast: classDef,
67864
68142
  externalMethodFiles: /* @__PURE__ */ new Map()
@@ -67889,6 +68167,7 @@ function makeOldStyleClassInfo(qualifiedName, baseName, constructorFile, methodF
67889
68167
  methodNames,
67890
68168
  staticMethodNames: /* @__PURE__ */ new Set(),
67891
68169
  constructorName: baseName,
68170
+ enumMembers: null,
67892
68171
  inferiorClasses: [],
67893
68172
  ast: null,
67894
68173
  isOldStyle: true,
@@ -90375,6 +90654,12 @@ function assertNoNonVoidBareExprStmts(spec) {
90375
90654
 
90376
90655
  // src/numbl-core/jit/lowering/classDefs.ts
90377
90656
  function registerClassDef(s, file, externalMethods) {
90657
+ if (s.members.some((m) => m.type === "Enumeration")) {
90658
+ throw new UnsupportedConstruct(
90659
+ `classdef '${s.name}' is an enumeration; enumeration classes are interpreter-only in the JIT`,
90660
+ s.span
90661
+ );
90662
+ }
90378
90663
  if (s.classAttributes.length > 0) {
90379
90664
  throw new UnsupportedConstruct(
90380
90665
  `classdef '${s.name}' has class attributes; not supported in v1`,
@@ -180,6 +180,8 @@ export declare class Interpreter {
180
180
  }>, args: unknown[], nargout: number) => unknown;
181
181
  instantiateClass: (className: string, args: unknown[], nargout: number) => unknown;
182
182
  interpretConstructor: (classInfo: ClassInfo, args: unknown[], nargout: number) => unknown;
183
+ interpretEnumMember: (className: string, memberName: string) => RuntimeValue | null;
184
+ convertToEnum: (className: string, classInfo: ClassInfo, args: unknown[]) => RuntimeValue;
183
185
  callUserFunction: (fn: FunctionDef, args: unknown[], nargout: number, narginOverride?: number) => unknown;
184
186
  callNestedFunction: (fn: FunctionDef, parentEnv: Environment, args: unknown[], nargout: number) => unknown;
185
187
  withFileContext: <T>(file: string, className: string | undefined, methodName: string | undefined, fn: () => T) => T;
@@ -3,6 +3,7 @@
3
3
  * Augments the Interpreter class via prototype assignment.
4
4
  */
5
5
  import type { Expr } from "../parser/types.js";
6
+ import type { RuntimeValue } from "../runtime/types.js";
6
7
  import { type ResolvedTarget } from "../functionResolve.js";
7
8
  import type { ClassInfo } from "../lowering/classInfo.js";
8
9
  import { Environment, type FunctionDef } from "./types.js";
@@ -30,6 +31,13 @@ export declare function interpretClassMethod(this: Interpreter, target: Extract<
30
31
  export declare function interpretPrivateFunction(this: Interpreter, target: Extract<ResolvedTarget, {
31
32
  kind: "privateFunction";
32
33
  }>, args: unknown[], nargout: number): unknown;
34
+ /** Resolve `ClassName.MemberName` to an enumeration-member instance. Returns
35
+ * null when `className` is not an enumeration or has no such member. */
36
+ export declare function interpretEnumMember(this: Interpreter, className: string, memberName: string): RuntimeValue | null;
37
+ /** Enumeration constructor-as-converter: `ClassName(x)` for an enumeration
38
+ * with no explicit constructor. Converts x — a member, a numeric value, a
39
+ * char/string member name, or an array thereof — to member(s). */
40
+ export declare function convertToEnum(this: Interpreter, className: string, classInfo: ClassInfo, args: unknown[]): RuntimeValue;
33
41
  export declare function instantiateClass(this: Interpreter, className: string, args: unknown[], nargout: number): unknown;
34
42
  export declare function interpretConstructor(this: Interpreter, classInfo: ClassInfo, args: unknown[], nargout: number): unknown;
35
43
  export declare function callUserFunction(this: Interpreter, fn: FunctionDef, args: unknown[], nargout: number, narginOverride?: number): unknown;
@@ -18,6 +18,12 @@ export interface ClassInfo {
18
18
  methodNames: Set<string>;
19
19
  staticMethodNames: Set<string>;
20
20
  constructorName: string | null;
21
+ /** Enumeration members in declaration order (name + constructor-arg
22
+ * exprs), or null when the class is not an enumeration. */
23
+ enumMembers: {
24
+ name: string;
25
+ args: Expr[];
26
+ }[] | null;
21
27
  ast: (Stmt & {
22
28
  type: "ClassDef";
23
29
  }) | null;
@@ -7,6 +7,11 @@ export declare class ClassParser extends FunctionParser {
7
7
  parseImport(): Stmt;
8
8
  parseClassDef(): Stmt;
9
9
  private parseNameBlock;
10
+ /** Parse an `enumeration ... end` member list. Each member is a bare
11
+ * identifier optionally followed by a parenthesized argument list
12
+ * (`quad (0)`, `Error (1, 0, 0)`). Members are separated by `;`/`,`/
13
+ * newline. The args are evaluated later (at member construction). */
14
+ private parseEnumerationBlock;
10
15
  private parsePropertiesNamesBlock;
11
16
  /** Consume a balanced `open ... close` run (handling nesting). Assumes the
12
17
  * next token is `open`; no-op otherwise. */
@@ -194,7 +194,13 @@ export type ClassMember = {
194
194
  } | {
195
195
  type: "Enumeration";
196
196
  attributes: Attr[];
197
- names: string[];
197
+ /** Enumeration members in declaration order. Each has a name and an
198
+ * optional argument list (`quad (0)` → args `[0]`). Args are passed
199
+ * to the constructor / first superclass to build the member value. */
200
+ members: {
201
+ name: string;
202
+ args: Expr[];
203
+ }[];
198
204
  } | {
199
205
  type: "Arguments";
200
206
  attributes: Attr[];
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Enumeration-class value semantics.
3
+ *
4
+ * An enumeration member is represented as a `RuntimeClassInstance` whose
5
+ * `_enumMember` field holds the member name and whose `_builtinData` holds the
6
+ * underlying (superclass) numeric value — e.g. for `classdef patchtype < uint32`
7
+ * the member `tri (1)` is stored with `_enumMember = "tri"`, `_builtinData = 1`.
8
+ *
9
+ * MATLAB comparison rules (verified against R2025b):
10
+ * - enum == enum → compare underlying values (inherited from the numeric
11
+ * superclass); falls back to name equality for plain
12
+ * enumerations that carry no superclass value.
13
+ * - enum == numeric → compare underlying value to the number.
14
+ * - enum == char/str → compare the member NAME to the text (a non-matching
15
+ * name yields false, never an error), NOT an
16
+ * element-wise char-code comparison.
17
+ * Comparisons are element-wise with scalar broadcasting; arrays of members
18
+ * produce a logical array the size of the member array.
19
+ */
20
+ import { type RuntimeValue, type RuntimeClassInstance } from "./types.js";
21
+ /** True if `v` is a single enumeration-member instance. */
22
+ export declare function isEnumInstance(v: RuntimeValue): v is RuntimeClassInstance;
23
+ /** True if `v` is an enumeration member or an array of enumeration members. */
24
+ export declare function isEnumValue(v: RuntimeValue): boolean;
25
+ /** Compare one enumeration member `e` against a scalar operand `other`
26
+ * (another member, a number, or char/string text). */
27
+ export declare function enumScalarEquals(e: RuntimeClassInstance, other: RuntimeValue): boolean;
28
+ /**
29
+ * Element-wise `==` / `~=` for enumeration operands. Returns a logical result
30
+ * (a boolean for scalars, a logical tensor for arrays), or null if the op is
31
+ * not equality/inequality or neither operand is an enumeration value.
32
+ */
33
+ export declare function enumEqualityOp(op: string, a: RuntimeValue, b: RuntimeValue): RuntimeValue | null;
@@ -123,7 +123,11 @@ export declare class RuntimeClassInstance extends Refcounted {
123
123
  /** For classes that inherit from built-in types (e.g. classdef Foo < double),
124
124
  * stores the underlying built-in data. */
125
125
  _builtinData: RuntimeValue | undefined;
126
- constructor(className: string, fields: Map<string, RuntimeValue>, isHandleClass: boolean, _builtinData?: RuntimeValue);
126
+ /** For enumeration-class members: the member name (e.g. "tri"). When set,
127
+ * this instance is an enumeration member and `_builtinData` holds its
128
+ * underlying (superclass) value. Drives ==/~=, switch, and char coercion. */
129
+ _enumMember: string | undefined;
130
+ constructor(className: string, fields: Map<string, RuntimeValue>, isHandleClass: boolean, _builtinData?: RuntimeValue, _enumMember?: string);
127
131
  /** Set/replace a field value (handle-class in-place mutation), with
128
132
  * proper decref-old / incref-new bookkeeping. Used for handle-class
129
133
  * field assigns; value-class field assigns construct a new instance. */
@@ -1,2 +1,2 @@
1
1
  /** Numbl version, used for JIT disk cache invalidation. */
2
- export declare const NUMBL_VERSION = "0.4.9";
2
+ export declare const NUMBL_VERSION = "0.4.11";