@ball-lang/compiler 1.5.4 → 1.5.5

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.
@@ -1 +1 @@
1
- {"version":3,"file":"preamble.d.ts","sourceRoot":"","sources":["../src/preamble.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,mBAAmB,QA2yC/B,CAAC"}
1
+ {"version":3,"file":"preamble.d.ts","sourceRoot":"","sources":["../src/preamble.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,mBAAmB,QAk1C/B,CAAC"}
package/dist/preamble.js CHANGED
@@ -133,8 +133,14 @@ class BallDouble {
133
133
 
134
134
  // Arithmetic helpers that propagate BallDouble through operations.
135
135
  // If either operand is BallDouble, the result is BallDouble (preserving .0).
136
+ //
137
+ // Operator-overload dispatch checks for __op_X__ methods (TRAILING double
138
+ // underscore) — this MUST match the Dart encoder's canonical operator naming
139
+ // (_canonicalOperatorName in dart/encoder/lib/encoder.dart), which always
140
+ // emits a trailing __. A single-underscore lookup (__op_mul) never matches,
141
+ // so overloaded operators silently fall through to raw JS arithmetic (#205).
136
142
  function __ball_mul(a: any, b: any): any {
137
- if (a != null && typeof a === 'object' && typeof a.__op_mul === 'function') return a.__op_mul(b);
143
+ if (a != null && typeof a === 'object' && typeof a.__op_mul__ === 'function') return a.__op_mul__(b);
138
144
  if (typeof a === 'bigint' || typeof b === 'bigint') return __i64_wrap(__to_bigint(a) * __to_bigint(b));
139
145
  const av = a instanceof BallDouble ? a.value : a;
140
146
  const bv = b instanceof BallDouble ? b.value : b;
@@ -142,7 +148,7 @@ function __ball_mul(a: any, b: any): any {
142
148
  return (a instanceof BallDouble || b instanceof BallDouble) ? new BallDouble(r) : r;
143
149
  }
144
150
  function __ball_add(a: any, b: any): any {
145
- if (a != null && typeof a === 'object' && typeof a.__op_add === 'function') return a.__op_add(b);
151
+ if (a != null && typeof a === 'object' && typeof a.__op_add__ === 'function') return a.__op_add__(b);
146
152
  if (typeof a === 'bigint' || typeof b === 'bigint') return __i64_wrap(__to_bigint(a) + __to_bigint(b));
147
153
  const av = a instanceof BallDouble ? a.value : a;
148
154
  const bv = b instanceof BallDouble ? b.value : b;
@@ -150,7 +156,7 @@ function __ball_add(a: any, b: any): any {
150
156
  return (a instanceof BallDouble || b instanceof BallDouble) ? new BallDouble(r) : r;
151
157
  }
152
158
  function __ball_sub(a: any, b: any): any {
153
- if (a != null && typeof a === 'object' && typeof a.__op_sub === 'function') return a.__op_sub(b);
159
+ if (a != null && typeof a === 'object' && typeof a.__op_sub__ === 'function') return a.__op_sub__(b);
154
160
  if (typeof a === 'bigint' || typeof b === 'bigint') return __i64_wrap(__to_bigint(a) - __to_bigint(b));
155
161
  const av = a instanceof BallDouble ? a.value : a;
156
162
  const bv = b instanceof BallDouble ? b.value : b;
@@ -160,7 +166,7 @@ function __ball_sub(a: any, b: any): any {
160
166
 
161
167
  // Dart equality: NaN != NaN, BallDouble value comparison, null == undefined.
162
168
  function __ball_eq(a: any, b: any): boolean {
163
- if (a != null && typeof a === 'object' && typeof a.__op_eq === 'function') return a.__op_eq(b);
169
+ if (a != null && typeof a === 'object' && typeof a.__op_eq__ === 'function') return a.__op_eq__(b);
164
170
  if (typeof a === 'bigint' || typeof b === 'bigint') {
165
171
  if (typeof a === 'bigint' && typeof b === 'bigint') return a === b;
166
172
  if (typeof a === 'bigint' && typeof b === 'number') return a === BigInt(b);
@@ -178,6 +184,39 @@ function __ball_eq(a: any, b: any): boolean {
178
184
  return a === b;
179
185
  }
180
186
 
187
+ // Relational operator-overload dispatch (less-than/greater-than/etc.),
188
+ // matching the __op_add__-style convention above. Unlike arithmetic,
189
+ // relational comparisons had NO overload attempt at all before #205 — every
190
+ // overloaded Vec2 < x etc. compiled straight to raw JS comparison.
191
+ function __ball_lt(a: any, b: any): any {
192
+ if (a != null && typeof a === 'object' && typeof a.__op_lt__ === 'function') return a.__op_lt__(b);
193
+ if (typeof a === 'bigint' || typeof b === 'bigint') return __to_bigint(a) < __to_bigint(b);
194
+ const av = a instanceof BallDouble ? a.value : a;
195
+ const bv = b instanceof BallDouble ? b.value : b;
196
+ return av < bv;
197
+ }
198
+ function __ball_gt(a: any, b: any): any {
199
+ if (a != null && typeof a === 'object' && typeof a.__op_gt__ === 'function') return a.__op_gt__(b);
200
+ if (typeof a === 'bigint' || typeof b === 'bigint') return __to_bigint(a) > __to_bigint(b);
201
+ const av = a instanceof BallDouble ? a.value : a;
202
+ const bv = b instanceof BallDouble ? b.value : b;
203
+ return av > bv;
204
+ }
205
+ function __ball_le(a: any, b: any): any {
206
+ if (a != null && typeof a === 'object' && typeof a.__op_le__ === 'function') return a.__op_le__(b);
207
+ if (typeof a === 'bigint' || typeof b === 'bigint') return __to_bigint(a) <= __to_bigint(b);
208
+ const av = a instanceof BallDouble ? a.value : a;
209
+ const bv = b instanceof BallDouble ? b.value : b;
210
+ return av <= bv;
211
+ }
212
+ function __ball_ge(a: any, b: any): any {
213
+ if (a != null && typeof a === 'object' && typeof a.__op_ge__ === 'function') return a.__op_ge__(b);
214
+ if (typeof a === 'bigint' || typeof b === 'bigint') return __to_bigint(a) >= __to_bigint(b);
215
+ const av = a instanceof BallDouble ? a.value : a;
216
+ const bv = b instanceof BallDouble ? b.value : b;
217
+ return av >= bv;
218
+ }
219
+
181
220
  function __ball_to_string(v: any): string {
182
221
  if (v === null || v === undefined) return 'null';
183
222
  if (typeof v === 'bigint') return v.toString();
@@ -1 +1 @@
1
- {"version":3,"file":"preamble.js","sourceRoot":"","sources":["../src/preamble.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2yC5C,CAAC"}
1
+ {"version":3,"file":"preamble.js","sourceRoot":"","sources":["../src/preamble.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAk1C5C,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ball-lang/compiler",
3
- "version": "1.5.4",
3
+ "version": "1.5.5",
4
4
  "description": "Ball → TypeScript compiler. Consumes a Ball protobuf Program and emits idiomatic TypeScript via ts-morph. The canonical TS compiler for Ball — lives in TS land so TS syntax knowledge doesn't leak into other languages.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/src/compiler.ts CHANGED
@@ -4245,10 +4245,13 @@ function __isUnknownFnError(e: any): boolean {
4245
4245
  if (l && r) return `!__ball_eq(${this.expr(l)}, ${this.expr(r)})`;
4246
4246
  return `!__ball_eq(${this.expr(fg("left", "value", "arg0")!)}, ${this.expr(fg("right", "other", "arg1")!)})`;
4247
4247
  }
4248
- case "less_than": return bin("<");
4249
- case "greater_than": return bin(">");
4250
- case "lte": return bin("<=");
4251
- case "gte": return bin(">=");
4248
+ // Relational comparisons route through __ball_lt/__ball_gt/__ball_le/__ball_ge
4249
+ // so operator-overloaded types (e.g. a class defining `<`) dispatch to
4250
+ // their `__op_lt__`-style method instead of raw JS comparison (#205).
4251
+ case "less_than": return `__ball_lt(${this.expr(fg("left", "value", "arg0")!)}, ${this.expr(fg("right", "other", "arg1")!)})`;
4252
+ case "greater_than": return `__ball_gt(${this.expr(fg("left", "value", "arg0")!)}, ${this.expr(fg("right", "other", "arg1")!)})`;
4253
+ case "lte": return `__ball_le(${this.expr(fg("left", "value", "arg0")!)}, ${this.expr(fg("right", "other", "arg1")!)})`;
4254
+ case "gte": return `__ball_ge(${this.expr(fg("left", "value", "arg0")!)}, ${this.expr(fg("right", "other", "arg1")!)})`;
4252
4255
  // Logical
4253
4256
  case "and": return bin("&&");
4254
4257
  case "or": return bin("||");
@@ -5870,6 +5873,12 @@ const KNOWN_PATTERN_KINDS = new Set([
5870
5873
  "ConstPattern", "VarPattern", "var", "WildcardPattern", "wildcard", "_",
5871
5874
  "ListPattern", "RestPattern", "rest", "record", "RecordPattern",
5872
5875
  "LogicalOrPattern", "CastPattern", "NullCheckPattern", "NullAssertPattern",
5876
+ // MapPattern/LogicalAndPattern/ObjectPattern/RelationalPattern were missing
5877
+ // (#206, #207) — every pattern kind the encoder can emit (see
5878
+ // dart/encoder/lib/encoder.dart's `_encodePattern`) must be listed here, or
5879
+ // it falls to the legacy cosmetic-text parser, which cannot round-trip
5880
+ // `final`-bound entries and other structured shapes.
5881
+ "MapPattern", "LogicalAndPattern", "ObjectPattern", "RelationalPattern",
5873
5882
  ]);
5874
5883
 
5875
5884
  /** Generate a JS type-check condition for a Dart type name against `subject`.
@@ -5960,6 +5969,102 @@ function compileStructuredPattern(
5960
5969
  }
5961
5970
  return undefined;
5962
5971
  }
5972
+ case "LogicalAndPattern": {
5973
+ // `p1 && p2` matches only if BOTH alternatives match against the SAME
5974
+ // subject (Dart's guard-combinator syntax, e.g. `int n && > 0`). Mirrors
5975
+ // LogicalOrPattern but ANDs the conditions instead of ORing them; bindings
5976
+ // from both sides are unioned (in practice the right operand of a
5977
+ // relational combinator binds nothing). Falls through (undefined) if
5978
+ // either operand isn't structured.
5979
+ const left = fields.get("left");
5980
+ const right = fields.get("right");
5981
+ if (left && right) {
5982
+ const lr = compileStructuredPattern(left, subject, exprFn);
5983
+ const rr = compileStructuredPattern(right, subject, exprFn);
5984
+ if (lr && rr) {
5985
+ return {
5986
+ condition: `(${lr.condition} && ${rr.condition})`,
5987
+ bindings: [...lr.bindings, ...rr.bindings],
5988
+ };
5989
+ }
5990
+ }
5991
+ return undefined;
5992
+ }
5993
+ case "RelationalPattern": {
5994
+ // `== x` / `!= x` / `> x` / `< x` / `>= x` / `<= x` as a standalone
5995
+ // (or LogicalAndPattern-combined) pattern. Numeric comparisons require
5996
+ // BOTH the subject and operand to be `num` (mirrors engine_std.dart's
5997
+ // `_matchRelationalPattern`) — a non-numeric subject simply fails to
5998
+ // match rather than throwing.
5999
+ const op = fields.get("operator")?.literal?.stringValue;
6000
+ const operandExpr = fields.get("operand");
6001
+ if (!op || !operandExpr) return { condition: "true", bindings: [] };
6002
+ const operandStr = exprFn(operandExpr);
6003
+ if (op === "==") return { condition: `__ball_eq(${subject}, ${operandStr})`, bindings: [] };
6004
+ if (op === "!=") return { condition: `(!__ball_eq(${subject}, ${operandStr}))`, bindings: [] };
6005
+ const isNum = (v: string) => `(typeof (${v}) === 'number' || (${v}) instanceof BallDouble)`;
6006
+ const numVal = (v: string) => `((${v}) instanceof BallDouble ? (${v}).value : (${v}))`;
6007
+ return {
6008
+ condition: `(${isNum(subject)} && ${isNum(operandStr)} && (${numVal(subject)} ${op} ${numVal(operandStr)}))`,
6009
+ bindings: [],
6010
+ };
6011
+ }
6012
+ case "MapPattern": {
6013
+ // `{'key': subpattern, ...}`. Ball maps compile to a plain JS object for
6014
+ // the common `map_create` path (bracket access, e.g. `{'k': 7}`) but to a
6015
+ // native `Map` for the `typed_map` path (`<K,V>{}` / `Map()`) — both
6016
+ // representations must match. A Ball Set compiles to a native `Set`
6017
+ // (set_create), which IS `typeof 'object'` and not an array, so it must
6018
+ // be explicitly excluded (issue #178's "MapPattern must exclude Set").
6019
+ // Extra keys beyond the ones listed are fine (unlike RecordPattern's
6020
+ // exact-arity rule) — mirrors engine_std.dart's 'map' case.
6021
+ const entries = fields.get("entries")?.literal?.listValue?.elements ?? [];
6022
+ const conds: string[] = [
6023
+ `(typeof ${subject} === 'object' && ${subject} !== null && !Array.isArray(${subject}) && !(${subject} instanceof Set))`,
6024
+ ];
6025
+ const binds: Array<{ varName: string; expr: string }> = [];
6026
+ for (const entry of entries) {
6027
+ const ef = mcFields(entry);
6028
+ const keyExpr = ef.get("key");
6029
+ const valuePattern = ef.get("value");
6030
+ if (!keyExpr) continue;
6031
+ const keyStr = exprFn(keyExpr);
6032
+ conds.push(`(${subject} instanceof Map ? ${subject}.has(${keyStr}) : (${keyStr} in ${subject}))`);
6033
+ if (valuePattern) {
6034
+ const valueExpr = `(${subject} instanceof Map ? ${subject}.get(${keyStr}) : ${subject}[${keyStr}])`;
6035
+ const sub = compileStructuredPattern(valuePattern, valueExpr, exprFn);
6036
+ if (sub) {
6037
+ if (sub.condition !== "true") conds.push(sub.condition);
6038
+ binds.push(...sub.bindings);
6039
+ }
6040
+ }
6041
+ }
6042
+ return { condition: conds.join(" && "), bindings: binds };
6043
+ }
6044
+ case "ObjectPattern": {
6045
+ // `Type(field: subpattern, ...)` matches by TYPE (reusing the same
6046
+ // instanceof/`__type__` gate CastPattern relies on via typeCheckCondition)
6047
+ // plus each named field's GETTER against its sub-pattern — field access,
6048
+ // not positional arity (mirrors engine_std.dart's 'object' case, which
6049
+ // reads `objMap[fieldName]` rather than requiring an exact key set).
6050
+ const typeName = fields.get("type")?.literal?.stringValue;
6051
+ const objFields = fields.get("fields")?.literal?.listValue?.elements ?? [];
6052
+ const conds: string[] = [];
6053
+ if (typeName) conds.push(typeCheckCondition(typeName, subject));
6054
+ const binds: Array<{ varName: string; expr: string }> = [];
6055
+ for (const of_ of objFields) {
6056
+ const ofFields = mcFields(of_);
6057
+ const fieldName = ofFields.get("name")?.literal?.stringValue;
6058
+ const fieldPattern = ofFields.get("pattern");
6059
+ if (!fieldName || !fieldPattern) continue;
6060
+ const sub = compileStructuredPattern(fieldPattern, `${subject}.${fieldName}`, exprFn);
6061
+ if (sub) {
6062
+ if (sub.condition !== "true") conds.push(sub.condition);
6063
+ binds.push(...sub.bindings);
6064
+ }
6065
+ }
6066
+ return { condition: conds.length > 0 ? conds.join(" && ") : "true", bindings: binds };
6067
+ }
5963
6068
  case "CastPattern": {
5964
6069
  // A cast pattern (subpat as T) ASSERTS the runtime type — it matches
5965
6070
  // structurally (like its sub-pattern) but THROWS on a mismatch (Dart
package/src/preamble.ts CHANGED
@@ -133,8 +133,14 @@ class BallDouble {
133
133
 
134
134
  // Arithmetic helpers that propagate BallDouble through operations.
135
135
  // If either operand is BallDouble, the result is BallDouble (preserving .0).
136
+ //
137
+ // Operator-overload dispatch checks for __op_X__ methods (TRAILING double
138
+ // underscore) — this MUST match the Dart encoder's canonical operator naming
139
+ // (_canonicalOperatorName in dart/encoder/lib/encoder.dart), which always
140
+ // emits a trailing __. A single-underscore lookup (__op_mul) never matches,
141
+ // so overloaded operators silently fall through to raw JS arithmetic (#205).
136
142
  function __ball_mul(a: any, b: any): any {
137
- if (a != null && typeof a === 'object' && typeof a.__op_mul === 'function') return a.__op_mul(b);
143
+ if (a != null && typeof a === 'object' && typeof a.__op_mul__ === 'function') return a.__op_mul__(b);
138
144
  if (typeof a === 'bigint' || typeof b === 'bigint') return __i64_wrap(__to_bigint(a) * __to_bigint(b));
139
145
  const av = a instanceof BallDouble ? a.value : a;
140
146
  const bv = b instanceof BallDouble ? b.value : b;
@@ -142,7 +148,7 @@ function __ball_mul(a: any, b: any): any {
142
148
  return (a instanceof BallDouble || b instanceof BallDouble) ? new BallDouble(r) : r;
143
149
  }
144
150
  function __ball_add(a: any, b: any): any {
145
- if (a != null && typeof a === 'object' && typeof a.__op_add === 'function') return a.__op_add(b);
151
+ if (a != null && typeof a === 'object' && typeof a.__op_add__ === 'function') return a.__op_add__(b);
146
152
  if (typeof a === 'bigint' || typeof b === 'bigint') return __i64_wrap(__to_bigint(a) + __to_bigint(b));
147
153
  const av = a instanceof BallDouble ? a.value : a;
148
154
  const bv = b instanceof BallDouble ? b.value : b;
@@ -150,7 +156,7 @@ function __ball_add(a: any, b: any): any {
150
156
  return (a instanceof BallDouble || b instanceof BallDouble) ? new BallDouble(r) : r;
151
157
  }
152
158
  function __ball_sub(a: any, b: any): any {
153
- if (a != null && typeof a === 'object' && typeof a.__op_sub === 'function') return a.__op_sub(b);
159
+ if (a != null && typeof a === 'object' && typeof a.__op_sub__ === 'function') return a.__op_sub__(b);
154
160
  if (typeof a === 'bigint' || typeof b === 'bigint') return __i64_wrap(__to_bigint(a) - __to_bigint(b));
155
161
  const av = a instanceof BallDouble ? a.value : a;
156
162
  const bv = b instanceof BallDouble ? b.value : b;
@@ -160,7 +166,7 @@ function __ball_sub(a: any, b: any): any {
160
166
 
161
167
  // Dart equality: NaN != NaN, BallDouble value comparison, null == undefined.
162
168
  function __ball_eq(a: any, b: any): boolean {
163
- if (a != null && typeof a === 'object' && typeof a.__op_eq === 'function') return a.__op_eq(b);
169
+ if (a != null && typeof a === 'object' && typeof a.__op_eq__ === 'function') return a.__op_eq__(b);
164
170
  if (typeof a === 'bigint' || typeof b === 'bigint') {
165
171
  if (typeof a === 'bigint' && typeof b === 'bigint') return a === b;
166
172
  if (typeof a === 'bigint' && typeof b === 'number') return a === BigInt(b);
@@ -178,6 +184,39 @@ function __ball_eq(a: any, b: any): boolean {
178
184
  return a === b;
179
185
  }
180
186
 
187
+ // Relational operator-overload dispatch (less-than/greater-than/etc.),
188
+ // matching the __op_add__-style convention above. Unlike arithmetic,
189
+ // relational comparisons had NO overload attempt at all before #205 — every
190
+ // overloaded Vec2 < x etc. compiled straight to raw JS comparison.
191
+ function __ball_lt(a: any, b: any): any {
192
+ if (a != null && typeof a === 'object' && typeof a.__op_lt__ === 'function') return a.__op_lt__(b);
193
+ if (typeof a === 'bigint' || typeof b === 'bigint') return __to_bigint(a) < __to_bigint(b);
194
+ const av = a instanceof BallDouble ? a.value : a;
195
+ const bv = b instanceof BallDouble ? b.value : b;
196
+ return av < bv;
197
+ }
198
+ function __ball_gt(a: any, b: any): any {
199
+ if (a != null && typeof a === 'object' && typeof a.__op_gt__ === 'function') return a.__op_gt__(b);
200
+ if (typeof a === 'bigint' || typeof b === 'bigint') return __to_bigint(a) > __to_bigint(b);
201
+ const av = a instanceof BallDouble ? a.value : a;
202
+ const bv = b instanceof BallDouble ? b.value : b;
203
+ return av > bv;
204
+ }
205
+ function __ball_le(a: any, b: any): any {
206
+ if (a != null && typeof a === 'object' && typeof a.__op_le__ === 'function') return a.__op_le__(b);
207
+ if (typeof a === 'bigint' || typeof b === 'bigint') return __to_bigint(a) <= __to_bigint(b);
208
+ const av = a instanceof BallDouble ? a.value : a;
209
+ const bv = b instanceof BallDouble ? b.value : b;
210
+ return av <= bv;
211
+ }
212
+ function __ball_ge(a: any, b: any): any {
213
+ if (a != null && typeof a === 'object' && typeof a.__op_ge__ === 'function') return a.__op_ge__(b);
214
+ if (typeof a === 'bigint' || typeof b === 'bigint') return __to_bigint(a) >= __to_bigint(b);
215
+ const av = a instanceof BallDouble ? a.value : a;
216
+ const bv = b instanceof BallDouble ? b.value : b;
217
+ return av >= bv;
218
+ }
219
+
181
220
  function __ball_to_string(v: any): string {
182
221
  if (v === null || v === undefined) return 'null';
183
222
  if (typeof v === 'bigint') return v.toString();