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.
@@ -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";
package/dist-cli/cli.js CHANGED
@@ -848,12 +848,17 @@ var RuntimeClassInstance = class extends Refcounted {
848
848
  /** For classes that inherit from built-in types (e.g. classdef Foo < double),
849
849
  * stores the underlying built-in data. */
850
850
  _builtinData;
851
- constructor(className, fields, isHandleClass2, _builtinData) {
851
+ /** For enumeration-class members: the member name (e.g. "tri"). When set,
852
+ * this instance is an enumeration member and `_builtinData` holds its
853
+ * underlying (superclass) value. Drives ==/~=, switch, and char coercion. */
854
+ _enumMember;
855
+ constructor(className, fields, isHandleClass2, _builtinData, _enumMember) {
852
856
  super();
853
857
  this.className = className;
854
858
  this.fields = fields;
855
859
  this.isHandleClass = isHandleClass2;
856
860
  this._builtinData = _builtinData;
861
+ this._enumMember = _enumMember;
857
862
  for (const v of fields.values()) incref(v);
858
863
  if (_builtinData !== void 0) incref(_builtinData);
859
864
  }
@@ -21670,6 +21675,10 @@ var valuesAreEqual = (a, b) => {
21670
21675
  return a === b;
21671
21676
  }
21672
21677
  case "class_instance": {
21678
+ const bi = b;
21679
+ if (a._enumMember !== void 0 || bi._enumMember !== void 0) {
21680
+ return a._enumMember === bi._enumMember && a.className === bi.className;
21681
+ }
21673
21682
  return a === b;
21674
21683
  }
21675
21684
  case "complex_number": {
@@ -22138,6 +22147,19 @@ function assignSlice(base, rowIndices, colIndices, rhs, curRows) {
22138
22147
  if (isRuntimeTensor(rhs)) {
22139
22148
  const nR = rowIndices.length;
22140
22149
  const nC = colIndices.length;
22150
+ if (rhs.data.length === 1) {
22151
+ const re = rhs.data[0];
22152
+ const im = rhs.imag ? rhs.imag[0] : 0;
22153
+ if (im !== 0 || base.imag) ensureImag(base);
22154
+ for (const r of rowIndices) {
22155
+ for (const c of colIndices) {
22156
+ const li = colMajorIndex(r, c, curRows);
22157
+ base.data[li] = re;
22158
+ if (base.imag) base.imag[li] = im;
22159
+ }
22160
+ }
22161
+ return;
22162
+ }
22141
22163
  const [rhsRows, rhsCols] = tensorSize2D(rhs);
22142
22164
  const shapeMatches = rhsRows === nR && rhsCols === nC;
22143
22165
  const sliceIsVector = nR === 1 || nC === 1;
@@ -29191,6 +29213,15 @@ function inferJitType(value) {
29191
29213
  fields
29192
29214
  };
29193
29215
  }
29216
+ if (isRuntimeClassInstanceArray(value)) {
29217
+ const arr = value;
29218
+ return {
29219
+ kind: "class_instance",
29220
+ className: arr.className,
29221
+ isHandleClass: false,
29222
+ fields: {}
29223
+ };
29224
+ }
29194
29225
  if (typeof value === "object" && value !== null && value.kind === "function") {
29195
29226
  return { kind: "function_handle" };
29196
29227
  }
@@ -32946,11 +32977,15 @@ var ClassParser = class extends FunctionParser {
32946
32977
  } else if (tok === 20 /* Enumeration */) {
32947
32978
  this.pos++;
32948
32979
  const attrs = this.parseOptionalAttrList();
32949
- const names = this.parseNameBlock();
32980
+ const enumMembers = this.parseEnumerationBlock();
32950
32981
  if (!this.consume(15 /* End */)) {
32951
32982
  throw this.error("expected 'end' after enumeration");
32952
32983
  }
32953
- members.push({ type: "Enumeration", attributes: attrs, names });
32984
+ members.push({
32985
+ type: "Enumeration",
32986
+ attributes: attrs,
32987
+ members: enumMembers
32988
+ });
32954
32989
  } else if (tok === 21 /* Arguments */) {
32955
32990
  this.pos++;
32956
32991
  const attrs = this.parseOptionalAttrList();
@@ -32991,6 +33026,38 @@ var ClassParser = class extends FunctionParser {
32991
33026
  }
32992
33027
  return names;
32993
33028
  }
33029
+ /** Parse an `enumeration ... end` member list. Each member is a bare
33030
+ * identifier optionally followed by a parenthesized argument list
33031
+ * (`quad (0)`, `Error (1, 0, 0)`). Members are separated by `;`/`,`/
33032
+ * newline. The args are evaluated later (at member construction). */
33033
+ parseEnumerationBlock() {
33034
+ const members = [];
33035
+ while (this.peekToken() !== void 0) {
33036
+ if (this.peekToken() === 15 /* End */) break;
33037
+ if (this.consume(49 /* Semicolon */) || this.consume(47 /* Comma */) || this.consume(68 /* Newline */)) {
33038
+ continue;
33039
+ }
33040
+ if (this.peekToken() !== 4 /* Ident */) break;
33041
+ const name = this.expectIdent();
33042
+ const args = [];
33043
+ if (this.consume(50 /* LParen */)) {
33044
+ if (this.peekToken() !== 51 /* RParen */) {
33045
+ args.push(this.parseExpr());
33046
+ while (this.consume(47 /* Comma */)) {
33047
+ args.push(this.parseExpr());
33048
+ }
33049
+ }
33050
+ if (!this.consume(51 /* RParen */)) {
33051
+ throw this.errorWithExpected(
33052
+ "expected ')' after enumeration member arguments",
33053
+ ")"
33054
+ );
33055
+ }
33056
+ }
33057
+ members.push({ name, args });
33058
+ }
33059
+ return members;
33060
+ }
32994
33061
  parsePropertiesNamesBlock() {
32995
33062
  const props = [];
32996
33063
  while (this.peekToken() !== void 0) {
@@ -33832,6 +33899,96 @@ function getConstant(name) {
33832
33899
  var COLON_SENTINEL = /* @__PURE__ */ Symbol("COLON");
33833
33900
  var END_SENTINEL = /* @__PURE__ */ Symbol("END");
33834
33901
 
33902
+ // src/numbl-core/runtime/runtimeEnum.ts
33903
+ function isEnumInstance(v) {
33904
+ return isRuntimeClassInstance(v) && v._enumMember !== void 0;
33905
+ }
33906
+ function isEnumValue(v) {
33907
+ if (isEnumInstance(v)) return true;
33908
+ if (isRuntimeClassInstanceArray(v))
33909
+ return v.elements.some((e) => e._enumMember !== void 0);
33910
+ return false;
33911
+ }
33912
+ function asText(v) {
33913
+ if (isRuntimeChar(v)) return v.value;
33914
+ if (isRuntimeString(v)) return v;
33915
+ return null;
33916
+ }
33917
+ function enumScalarEquals(e, other) {
33918
+ const text = asText(other);
33919
+ if (text !== null) return e._enumMember === text;
33920
+ if (isEnumInstance(other)) {
33921
+ if (e._builtinData !== void 0 && other._builtinData !== void 0) {
33922
+ return toNumber(e._builtinData) === toNumber(other._builtinData);
33923
+ }
33924
+ return e._enumMember === other._enumMember;
33925
+ }
33926
+ if (e._builtinData !== void 0) {
33927
+ if (isRuntimeNumber(other)) return toNumber(e._builtinData) === other;
33928
+ if (isRuntimeLogical(other))
33929
+ return toNumber(e._builtinData) === (other ? 1 : 0);
33930
+ }
33931
+ return false;
33932
+ }
33933
+ function decompose(v) {
33934
+ if (isEnumInstance(v)) return { elems: [v], shape: [1, 1] };
33935
+ if (isRuntimeClassInstanceArray(v))
33936
+ return { elems: [...v.elements], shape: v.shape };
33937
+ if (isRuntimeChar(v)) return { elems: [v], shape: [1, 1] };
33938
+ if (isRuntimeString(v)) return { elems: [v], shape: [1, 1] };
33939
+ if (isRuntimeStringArray(v))
33940
+ return { elems: v.data.map((s) => s), shape: v.shape };
33941
+ if (isRuntimeNumber(v) || isRuntimeLogical(v))
33942
+ return { elems: [v], shape: [1, 1] };
33943
+ if (isRuntimeTensor(v)) {
33944
+ const shape = [v.shape[0] ?? 0, v.shape[1] ?? 1];
33945
+ return { elems: Array.from(v.data, (x) => x), shape };
33946
+ }
33947
+ return null;
33948
+ }
33949
+ function elementEquals(x, y) {
33950
+ if (isEnumInstance(x)) return enumScalarEquals(x, y);
33951
+ if (isEnumInstance(y)) return enumScalarEquals(y, x);
33952
+ return false;
33953
+ }
33954
+ function enumEqualityOp(op, a, b) {
33955
+ if (op !== "Equal" /* Equal */ && op !== "NotEqual" /* NotEqual */)
33956
+ return null;
33957
+ if (!isEnumValue(a) && !isEnumValue(b)) return null;
33958
+ const oa = decompose(a);
33959
+ const ob = decompose(b);
33960
+ if (!oa || !ob) return null;
33961
+ const na = oa.elems.length;
33962
+ const nb = ob.elems.length;
33963
+ let n;
33964
+ let shape;
33965
+ if (na === nb) {
33966
+ n = na;
33967
+ shape = oa.shape;
33968
+ } else if (na === 1) {
33969
+ n = nb;
33970
+ shape = ob.shape;
33971
+ } else if (nb === 1) {
33972
+ n = na;
33973
+ shape = oa.shape;
33974
+ } else {
33975
+ throw new RuntimeError("Array dimensions must match for comparison");
33976
+ }
33977
+ const negate = op === "NotEqual" /* NotEqual */;
33978
+ const out = new Float64Array(n);
33979
+ for (let i = 0; i < n; i++) {
33980
+ const eq2 = elementEquals(
33981
+ oa.elems[na === 1 ? 0 : i],
33982
+ ob.elems[nb === 1 ? 0 : i]
33983
+ );
33984
+ out[i] = (negate ? !eq2 : eq2) ? 1 : 0;
33985
+ }
33986
+ if (n === 1) return RTV.logical(out[0] === 1);
33987
+ const t = RTV.tensor(out, [shape[0], shape[1]]);
33988
+ t._isLogical = true;
33989
+ return t;
33990
+ }
33991
+
33835
33992
  // src/numbl-core/runtime/runtimeHelpers.ts
33836
33993
  function isDeferredRange(v) {
33837
33994
  return typeof v === "object" && v !== null && v["_deferredRange"] === true;
@@ -33856,6 +34013,8 @@ function ensureRuntimeValue(v) {
33856
34013
  return v;
33857
34014
  }
33858
34015
  function switchValuesMatch(a, b) {
34016
+ if (isEnumInstance(a)) return enumScalarEquals(a, b);
34017
+ if (isEnumInstance(b)) return enumScalarEquals(b, a);
33859
34018
  if ((isRuntimeChar(a) || isRuntimeString(a)) && (isRuntimeChar(b) || isRuntimeString(b))) {
33860
34019
  return (typeof a === "string" ? a : a.value) === (typeof b === "string" ? b : b.value);
33861
34020
  }
@@ -37293,7 +37452,16 @@ function indexStore(rt, base, indices, rhs, skipSubsasgn = false) {
37293
37452
  }
37294
37453
  existing[k] = rhsInst;
37295
37454
  if (existing.length === 1) return existing[0];
37296
- return RTV.classInstanceArray(className, existing, [1, existing.length]);
37455
+ const origShape = isRuntimeClassInstanceArray(mv) ? mv.shape : null;
37456
+ let shape;
37457
+ if (origShape && existing.length === origShape[0] * origShape[1]) {
37458
+ shape = origShape;
37459
+ } else if (origShape && origShape[1] === 1) {
37460
+ shape = [existing.length, 1];
37461
+ } else {
37462
+ shape = [1, existing.length];
37463
+ }
37464
+ return RTV.classInstanceArray(className, existing, shape);
37297
37465
  }
37298
37466
  }
37299
37467
  if (isRuntimeClassInstance(mv)) {
@@ -46532,6 +46700,8 @@ function sparseToDense2(S) {
46532
46700
  return RTV.tensor(data, [S.m, S.n], imag2);
46533
46701
  }
46534
46702
  function valuesEqualSimple(a, b) {
46703
+ if (isEnumInstance(a)) return enumScalarEquals(a, b);
46704
+ if (isEnumInstance(b)) return enumScalarEquals(b, a);
46535
46705
  {
46536
46706
  const aText = textValue(a);
46537
46707
  const bText = textValue(b);
@@ -54720,7 +54890,8 @@ function copyClassInstance(inst) {
54720
54890
  inst.className,
54721
54891
  new Map(inst.fields),
54722
54892
  inst.isHandleClass,
54723
- inst._builtinData
54893
+ inst._builtinData,
54894
+ inst._enumMember
54724
54895
  );
54725
54896
  }
54726
54897
  function repelemObjects(v, repArgs) {
@@ -57808,6 +57979,15 @@ defineBuiltin({
57808
57979
  });
57809
57980
 
57810
57981
  // src/numbl-core/interpreter/builtins/type-constructors.ts
57982
+ function classInstanceArrayNumeric(v) {
57983
+ if (!isRuntimeClassInstanceArray(v)) return null;
57984
+ const data = allocFloat64Array(v.elements.length);
57985
+ for (let i = 0; i < v.elements.length; i++) {
57986
+ const bd = v.elements[i]._builtinData;
57987
+ data[i] = bd !== void 0 ? toNumber(bd) : NaN;
57988
+ }
57989
+ return RTV.tensor(data, [v.shape[0], v.shape[1]]);
57990
+ }
57811
57991
  function parseDoubleText(s) {
57812
57992
  const t = s.trim();
57813
57993
  if (t === "") return NaN;
@@ -57862,6 +58042,8 @@ defineBuiltin({
57862
58042
  if (isRuntimeClassInstance(v) && v._builtinData !== void 0) {
57863
58043
  return v._builtinData;
57864
58044
  }
58045
+ const arr = classInstanceArrayNumeric(v);
58046
+ if (arr) return arr;
57865
58047
  return RTV.num(toNumber(v));
57866
58048
  }
57867
58049
  }
@@ -57889,6 +58071,8 @@ function singleApply(v) {
57889
58071
  }
57890
58072
  if (isRuntimeClassInstance(v) && v._builtinData !== void 0)
57891
58073
  return singleApply(v._builtinData);
58074
+ const arr = classInstanceArrayNumeric(v);
58075
+ if (arr) return singleApply(arr);
57892
58076
  return RTV.num(Math.fround(toNumber(v)));
57893
58077
  }
57894
58078
  defineBuiltin({
@@ -57948,7 +58132,7 @@ for (const { name, min: min2, max: max2 } of INT_RANGES) {
57948
58132
  match: (argTypes) => {
57949
58133
  if (argTypes.length !== 1) return null;
57950
58134
  const a = argTypes[0];
57951
- if (a.kind === "number" || a.kind === "boolean" || a.kind === "char" || a.kind === "complex_or_number")
58135
+ if (a.kind === "number" || a.kind === "boolean" || a.kind === "char" || a.kind === "complex_or_number" || a.kind === "class_instance")
57952
58136
  return [{ kind: "number" }];
57953
58137
  if (a.kind === "tensor")
57954
58138
  return [
@@ -57962,7 +58146,7 @@ for (const { name, min: min2, max: max2 } of INT_RANGES) {
57962
58146
  return null;
57963
58147
  },
57964
58148
  apply: (args) => {
57965
- const v = args[0];
58149
+ const v = isRuntimeClassInstance(args[0]) && args[0]._builtinData !== void 0 ? args[0]._builtinData : classInstanceArrayNumeric(args[0]) ?? args[0];
57966
58150
  if (isRuntimeNumber(v))
57967
58151
  return RTV.num(saturateRoundToward(v, min2, max2));
57968
58152
  if (isRuntimeLogical(v)) return RTV.num(v ? 1 : 0);
@@ -58217,7 +58401,7 @@ defineBuiltin({
58217
58401
  match: (argTypes) => {
58218
58402
  if (argTypes.length !== 1) return null;
58219
58403
  const a = argTypes[0];
58220
- if (a.kind === "number" || a.kind === "boolean")
58404
+ if (a.kind === "number" || a.kind === "boolean" || a.kind === "class_instance")
58221
58405
  return [{ kind: "boolean" }];
58222
58406
  if (a.kind === "tensor")
58223
58407
  return [
@@ -58232,7 +58416,7 @@ defineBuiltin({
58232
58416
  return null;
58233
58417
  },
58234
58418
  apply: (args) => {
58235
- const v = args[0];
58419
+ const v = isRuntimeClassInstance(args[0]) && args[0]._builtinData !== void 0 ? args[0]._builtinData : classInstanceArrayNumeric(args[0]) ?? args[0];
58236
58420
  if (isRuntimeTensor(v)) {
58237
58421
  const result = allocFloat64Array(v.data.length);
58238
58422
  for (let i = 0; i < v.data.length; i++) {
@@ -63891,7 +64075,7 @@ function getSourceLine(getSource, file, line) {
63891
64075
  }
63892
64076
 
63893
64077
  // src/numbl-core/version.ts
63894
- var NUMBL_VERSION = "0.4.9";
64078
+ var NUMBL_VERSION = "0.4.11";
63895
64079
 
63896
64080
  // src/cli-repl.ts
63897
64081
  import { createInterface } from "readline";
@@ -66402,6 +66586,8 @@ function evalBinary(expr) {
66402
66586
  const right = this.evalExpr(expr.right);
66403
66587
  const lv = ensureRuntimeValue(left);
66404
66588
  const rv = ensureRuntimeValue(right);
66589
+ const enumResult = enumEqualityOp(expr.op, lv, rv);
66590
+ if (enumResult !== null) return enumResult;
66405
66591
  if (isRuntimeClassInstance(lv) || isRuntimeClassInstance(rv)) {
66406
66592
  return this.rt.binop(expr.op, left, right);
66407
66593
  }
@@ -66506,6 +66692,8 @@ function evalMember(expr, nargout) {
66506
66692
  if (lastDot > 0) {
66507
66693
  const prefix = dottedName.slice(0, lastDot);
66508
66694
  const methodName = dottedName.slice(lastDot + 1);
66695
+ const enumMember = this.interpretEnumMember(prefix, methodName);
66696
+ if (enumMember !== null) return enumMember;
66509
66697
  if (this.functionIndex.classStaticMethods.get(prefix)?.has(methodName) || // Implicit static `ClassName.empty` (handled by interpretClassMethod)
66510
66698
  methodName === "empty" && this.functionIndex.workspaceClasses.has(prefix)) {
66511
66699
  const target = {
@@ -66965,6 +67153,7 @@ __export(interpreterFunctions_exports, {
66965
67153
  callNestedFunction: () => callNestedFunction,
66966
67154
  callUserFunction: () => callUserFunction,
66967
67155
  collectClassProperties: () => collectClassProperties,
67156
+ convertToEnum: () => convertToEnum,
66968
67157
  declaredNargin: () => declaredNargin,
66969
67158
  evalInLocalScope: () => evalInLocalScope,
66970
67159
  findExternalMethod: () => findExternalMethod,
@@ -66976,6 +67165,7 @@ __export(interpreterFunctions_exports, {
66976
67165
  instantiateClass: () => instantiateClass,
66977
67166
  interpretClassMethod: () => interpretClassMethod,
66978
67167
  interpretConstructor: () => interpretConstructor,
67168
+ interpretEnumMember: () => interpretEnumMember,
66979
67169
  interpretJsUserFunction: () => interpretJsUserFunction,
66980
67170
  interpretLocalFunction: () => interpretLocalFunction,
66981
67171
  interpretPrivateFunction: () => interpretPrivateFunction,
@@ -67805,11 +67995,95 @@ function interpretPrivateFunction(target, args, nargout) {
67805
67995
  }
67806
67996
  throw new RuntimeError(`Private function '${target.name}' not found`);
67807
67997
  }
67998
+ var NUMERIC_ENUM_SUPERS = /* @__PURE__ */ new Set([
67999
+ "double",
68000
+ "single",
68001
+ "int8",
68002
+ "int16",
68003
+ "int32",
68004
+ "int64",
68005
+ "uint8",
68006
+ "uint16",
68007
+ "uint32",
68008
+ "uint64",
68009
+ "logical",
68010
+ "char"
68011
+ ]);
68012
+ function enumUnderlyingValue(interp, classInfo, argExprs) {
68013
+ if (argExprs.length === 0) return void 0;
68014
+ const first = ensureRuntimeValue(interp.evalExpr(argExprs[0]));
68015
+ const sc = classInfo.superClass;
68016
+ if (sc && NUMERIC_ENUM_SUPERS.has(sc)) {
68017
+ return ensureRuntimeValue(interp.rt.callBuiltin(sc, 1, [first]));
68018
+ }
68019
+ return first;
68020
+ }
68021
+ function interpretEnumMember(className, memberName) {
68022
+ const classInfo = this.ctx.getClassInfo(className);
68023
+ if (!classInfo || !classInfo.enumMembers) return null;
68024
+ const memberDef = classInfo.enumMembers.find((m) => m.name === memberName);
68025
+ if (!memberDef) return null;
68026
+ const value = enumUnderlyingValue(this, classInfo, memberDef.args);
68027
+ return new RuntimeClassInstance(
68028
+ className,
68029
+ /* @__PURE__ */ new Map(),
68030
+ false,
68031
+ value,
68032
+ memberName
68033
+ );
68034
+ }
68035
+ function convertToEnum(className, classInfo, args) {
68036
+ if (args.length === 0) {
68037
+ throw new RuntimeError(
68038
+ `${className}: enumeration requires an argument to construct`
68039
+ );
68040
+ }
68041
+ const val = ensureRuntimeValue(args[0]);
68042
+ if (isRuntimeClassInstance(val) && val._enumMember !== void 0) return val;
68043
+ if (isRuntimeClassInstanceArray(val)) return val;
68044
+ const members = classInfo.enumMembers.map((m) => ({
68045
+ name: m.name,
68046
+ value: enumUnderlyingValue(this, classInfo, m.args)
68047
+ }));
68048
+ const makeMember = (name, value) => new RuntimeClassInstance(className, /* @__PURE__ */ new Map(), false, value, name);
68049
+ const fromName = (text) => {
68050
+ const m = members.find((mm) => mm.name === text);
68051
+ if (!m)
68052
+ throw new RuntimeError(
68053
+ `'${text}' is not a valid member of enumeration '${className}'`
68054
+ );
68055
+ return makeMember(m.name, m.value);
68056
+ };
68057
+ const fromNumber = (num) => {
68058
+ const m = members.find(
68059
+ (mm) => mm.value !== void 0 && toNumber(mm.value) === num
68060
+ );
68061
+ if (!m)
68062
+ throw new RuntimeError(
68063
+ `${num} is not a valid underlying value for enumeration '${className}'`
68064
+ );
68065
+ return makeMember(m.name, m.value);
68066
+ };
68067
+ if (isRuntimeChar(val)) return fromName(val.value);
68068
+ if (isRuntimeString(val)) return fromName(val);
68069
+ if (isRuntimeNumber(val)) return fromNumber(val);
68070
+ if (isRuntimeLogical(val)) return fromNumber(val ? 1 : 0);
68071
+ if (isRuntimeTensor(val)) {
68072
+ const elems = Array.from(val.data, (x) => fromNumber(x));
68073
+ if (elems.length === 1) return elems[0];
68074
+ const shape = [val.shape[0] ?? 0, val.shape[1] ?? 1];
68075
+ return RTV.classInstanceArray(className, elems, shape);
68076
+ }
68077
+ throw new RuntimeError(`Cannot convert value to enumeration '${className}'`);
68078
+ }
67808
68079
  function instantiateClass(className, args, nargout) {
67809
68080
  const classInfo = this.ctx.getClassInfo(className);
67810
68081
  if (!classInfo) {
67811
68082
  return this.rt.callClassMethod(className, className, nargout, args);
67812
68083
  }
68084
+ if (classInfo.enumMembers && args.length > 0) {
68085
+ return this.convertToEnum(className, classInfo, args);
68086
+ }
67813
68087
  if (classInfo.isOldStyle) {
67814
68088
  const ctorName = classInfo.constructorName ?? className;
67815
68089
  const ctorFn = this.findExternalMethod(classInfo, ctorName);
@@ -68610,6 +68884,7 @@ function extractClassInfo(classDef, qualifiedName, fileName, source) {
68610
68884
  const methodNames = /* @__PURE__ */ new Set();
68611
68885
  const staticMethodNames = /* @__PURE__ */ new Set();
68612
68886
  let constructorName = null;
68887
+ let enumMembers = null;
68613
68888
  const dotIdx = qualifiedName.lastIndexOf(".");
68614
68889
  const baseName = dotIdx >= 0 ? qualifiedName.slice(dotIdx + 1) : qualifiedName;
68615
68890
  for (const member of classDef.members) {
@@ -68651,6 +68926,8 @@ function extractClassInfo(classDef, qualifiedName, fileName, source) {
68651
68926
  }
68652
68927
  }
68653
68928
  }
68929
+ } else if (member.type === "Enumeration") {
68930
+ enumMembers = member.members.map((m) => ({ name: m.name, args: m.args }));
68654
68931
  }
68655
68932
  }
68656
68933
  const inferiorClasses = [];
@@ -68673,6 +68950,7 @@ function extractClassInfo(classDef, qualifiedName, fileName, source) {
68673
68950
  methodNames,
68674
68951
  staticMethodNames,
68675
68952
  constructorName,
68953
+ enumMembers,
68676
68954
  inferiorClasses,
68677
68955
  ast: classDef,
68678
68956
  externalMethodFiles: /* @__PURE__ */ new Map()
@@ -68703,6 +68981,7 @@ function makeOldStyleClassInfo(qualifiedName, baseName, constructorFile, methodF
68703
68981
  methodNames,
68704
68982
  staticMethodNames: /* @__PURE__ */ new Set(),
68705
68983
  constructorName: baseName,
68984
+ enumMembers: null,
68706
68985
  inferiorClasses: [],
68707
68986
  ast: null,
68708
68987
  isOldStyle: true,
@@ -94502,6 +94781,12 @@ function assertNoNonVoidBareExprStmts2(spec) {
94502
94781
 
94503
94782
  // src/numbl-core/jit/lowering/classDefs.ts
94504
94783
  function registerClassDef(s, file, externalMethods) {
94784
+ if (s.members.some((m) => m.type === "Enumeration")) {
94785
+ throw new UnsupportedConstruct(
94786
+ `classdef '${s.name}' is an enumeration; enumeration classes are interpreter-only in the JIT`,
94787
+ s.span
94788
+ );
94789
+ }
94505
94790
  if (s.classAttributes.length > 0) {
94506
94791
  throw new UnsupportedConstruct(
94507
94792
  `classdef '${s.name}' has class attributes; not supported in v1`,