@ball-lang/compiler 1.6.2 → 1.6.4
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/compiler.d.ts.map +1 -1
- package/dist/compiler.js +34 -6
- package/dist/compiler.js.map +1 -1
- package/dist/preamble.d.ts.map +1 -1
- package/dist/preamble.js +86 -12
- package/dist/preamble.js.map +1 -1
- package/package.json +1 -1
- package/src/compiler.ts +35 -6
- package/src/preamble.ts +86 -12
package/dist/preamble.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preamble.d.ts","sourceRoot":"","sources":["../src/preamble.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,mBAAmB,
|
|
1
|
+
{"version":3,"file":"preamble.d.ts","sourceRoot":"","sources":["../src/preamble.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,mBAAmB,QA67C/B,CAAC"}
|
package/dist/preamble.js
CHANGED
|
@@ -110,7 +110,15 @@ class BallObject extends BallMap {
|
|
|
110
110
|
// (e.g. 42.0 not 42). Used by the compiled Dart engine's _toDouble.
|
|
111
111
|
class BallDouble {
|
|
112
112
|
readonly value: number;
|
|
113
|
-
|
|
113
|
+
// Collapse nested wrapping (new BallDouble(new BallDouble(x))) down to the
|
|
114
|
+
// innermost raw number instead of storing a BallDouble-holding-a-BallDouble.
|
|
115
|
+
// This can legitimately arise now that string_to_double's OWN compiled
|
|
116
|
+
// form already wraps its result (#222): the self-hosted engine's Dart
|
|
117
|
+
// source explicitly does BallDouble(double.parse(s)) to make the parse
|
|
118
|
+
// result double-preserving, which — once double.parse (string_to_double)
|
|
119
|
+
// started wrapping on its own — would otherwise double-wrap. Unwrapping
|
|
120
|
+
// here is idempotent for every other caller, since v is a plain number.
|
|
121
|
+
constructor(v: number) { this.value = v instanceof BallDouble ? v.value : v; }
|
|
114
122
|
valueOf(): number { return this.value; }
|
|
115
123
|
get isNaN(): boolean { return Number.isNaN(this.value); }
|
|
116
124
|
get isFinite(): boolean { return Number.isFinite(this.value); }
|
|
@@ -244,6 +252,13 @@ function __ball_to_string(v: any): string {
|
|
|
244
252
|
}
|
|
245
253
|
return '{' + parts.join(', ') + '}';
|
|
246
254
|
}
|
|
255
|
+
if (v instanceof Set) {
|
|
256
|
+
// A Set is a plain object from typeof's perspective (falls through to
|
|
257
|
+
// the generic branch below, which reads Object.keys — always [] for a
|
|
258
|
+
// Set's internal slots), so every Set printed as the empty "{}" no
|
|
259
|
+
// matter its contents until this dedicated case was added (#219).
|
|
260
|
+
return '{' + [...v].map(__ball_to_string).join(', ') + '}';
|
|
261
|
+
}
|
|
247
262
|
if (typeof v === 'object' && !Array.isArray(v)) {
|
|
248
263
|
// StringBuffer-like objects
|
|
249
264
|
if (v['__buffer__'] && Array.isArray(v['__buffer__'])) {
|
|
@@ -296,10 +311,13 @@ function __ball_to_int(v: any): any {
|
|
|
296
311
|
return t;
|
|
297
312
|
}
|
|
298
313
|
|
|
299
|
-
|
|
314
|
+
// Returns a BallDouble (not a bare number) so a whole-valued result (e.g.
|
|
315
|
+
// double.parse('7.0')) still prints "7.0", not "7" — JS numbers erase the
|
|
316
|
+
// int/double distinction that the wrapper exists to preserve (#67/#222).
|
|
317
|
+
function __ball_parse_double(s: string): BallDouble {
|
|
300
318
|
const n = parseFloat(s);
|
|
301
319
|
if (Number.isNaN(n)) throw new Error('FormatException: ' + s);
|
|
302
|
-
return n;
|
|
320
|
+
return new BallDouble(n);
|
|
303
321
|
}
|
|
304
322
|
|
|
305
323
|
function __ball_double_to_string(n: number): string {
|
|
@@ -307,6 +325,16 @@ function __ball_double_to_string(n: number): string {
|
|
|
307
325
|
return n.toString();
|
|
308
326
|
}
|
|
309
327
|
|
|
328
|
+
// num.toStringAsFixed(digits). JS Number.prototype.toFixed drops the sign of
|
|
329
|
+
// -0 (returns "0.00" not "-0.00"); Dart's toStringAsFixed keeps it, matching
|
|
330
|
+
// the -0 handling __ball_to_string/BallDouble.toString already do.
|
|
331
|
+
function __ball_to_fixed(v: any, digits: any): string {
|
|
332
|
+
const n = Number(v);
|
|
333
|
+
const s = n.toFixed(digits);
|
|
334
|
+
if (n === 0 && 1 / n === -Infinity && !s.startsWith('-')) return '-' + s;
|
|
335
|
+
return s;
|
|
336
|
+
}
|
|
337
|
+
|
|
310
338
|
// Polymorphic concat / merge used for std.list_concat. The encoder emits
|
|
311
339
|
// list_concat both for Dart list concat AND for Map.addAll(...) (encoded as
|
|
312
340
|
// m = list_concat(m, other)). Arrays concat positionally; plain objects
|
|
@@ -350,31 +378,77 @@ function __ball_push_all(target: any, items: any): void {
|
|
|
350
378
|
// demotes back to Number when the result fits in MAX_SAFE_INTEGER.
|
|
351
379
|
const __I64_MAX = 9223372036854775807n;
|
|
352
380
|
const __I64_MIN = -9223372036854775808n;
|
|
353
|
-
const __I64_MOD = 18446744073709551616n;
|
|
354
381
|
function __i64_wrap(v: bigint): any {
|
|
355
|
-
|
|
356
|
-
|
|
382
|
+
// Two's-complement wrap to the signed 64-bit range — BigInt.asIntN(64, v)
|
|
383
|
+
// is the idiomatic builtin for exactly this (equivalent to the old manual
|
|
384
|
+
// modulo-then-resign, verified against it across boundary/overflow cases).
|
|
385
|
+
v = BigInt.asIntN(64, v);
|
|
357
386
|
if (v >= -9007199254740991n && v <= 9007199254740991n) return Number(v);
|
|
358
387
|
return v;
|
|
359
388
|
}
|
|
360
389
|
function __to_bigint(v: any): bigint {
|
|
361
390
|
if (typeof v === 'bigint') return v;
|
|
391
|
+
// Matches the reference Dart engine's _toInt (engine_std.dart), which
|
|
392
|
+
// falls through to 0 for anything that isn't an int/BallInt/double/
|
|
393
|
+
// BallDouble/String/bool -- including null. NaN is deliberately NOT
|
|
394
|
+
// special-cased here: Dart's double.toInt() throws on NaN (via
|
|
395
|
+
// _ballDoubleToInt64), and BigInt(NaN) already throws for the same
|
|
396
|
+
// reason (RangeError: not an integer), so that path already fails loud
|
|
397
|
+
// consistently with the reference engine without any extra handling.
|
|
398
|
+
if (v === null || v === undefined) return 0n;
|
|
362
399
|
if (v instanceof BallDouble) return BigInt(Math.trunc(v.value));
|
|
363
400
|
return BigInt(v);
|
|
364
401
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
402
|
+
// Fast-path guard: true when v is a plain (non-bigint, non-BallDouble)
|
|
403
|
+
// integer within the signed 32-bit range. AND/OR/XOR/NOT never grow a
|
|
404
|
+
// result past its operands' bit width, so when both operands fit in 32
|
|
405
|
+
// bits, JS's native 32-bit bitwise operators give a result numerically
|
|
406
|
+
// IDENTICAL to the full 64-bit BigInt path (sign-extending a 32-bit value
|
|
407
|
+
// to 64 bits before AND/OR/XOR/NOT never changes the low 32 result bits,
|
|
408
|
+
// and — verified across the full boundary range — never changes whether
|
|
409
|
+
// the high bits are the correct sign-extension of them either). Left/right
|
|
410
|
+
// shift are NOT given this fast path: shifting can grow a result past 32
|
|
411
|
+
// bits even when the input operand fits (e.g. large_int << 40), so their
|
|
412
|
+
// overflow behavior isn't safely 32-bit-local the way AND/OR/XOR/NOT is.
|
|
413
|
+
function __fits32(v: any): boolean {
|
|
414
|
+
return typeof v === 'number' && Number.isInteger(v) && v >= -2147483648 && v <= 2147483647;
|
|
415
|
+
}
|
|
416
|
+
function __ball_bitand(a: any, b: any): any {
|
|
417
|
+
if (__fits32(a) && __fits32(b)) return a & b;
|
|
418
|
+
return __i64_wrap(__to_bigint(a) & __to_bigint(b));
|
|
419
|
+
}
|
|
420
|
+
function __ball_bitor(a: any, b: any): any {
|
|
421
|
+
if (__fits32(a) && __fits32(b)) return a | b;
|
|
422
|
+
return __i64_wrap(__to_bigint(a) | __to_bigint(b));
|
|
423
|
+
}
|
|
424
|
+
function __ball_bitxor(a: any, b: any): any {
|
|
425
|
+
if (__fits32(a) && __fits32(b)) return a ^ b;
|
|
426
|
+
return __i64_wrap(__to_bigint(a) ^ __to_bigint(b));
|
|
427
|
+
}
|
|
428
|
+
function __ball_bitnot(a: any): any {
|
|
429
|
+
if (__fits32(a)) return ~a;
|
|
430
|
+
return __i64_wrap(~__to_bigint(a));
|
|
431
|
+
}
|
|
369
432
|
function __ball_shl(a: any, b: any): any { return __i64_wrap(__to_bigint(a) << __to_bigint(b)); }
|
|
370
433
|
function __ball_shr(a: any, b: any): any { return __i64_wrap(__to_bigint(a) >> __to_bigint(b)); }
|
|
371
434
|
// Unsigned/logical shift: reinterpret a as an unsigned 64-bit value (add
|
|
372
435
|
// 2^64 if negative) before shifting, so zeros fill from the left instead of
|
|
373
436
|
// the sign bit — unlike >>> on raw JS numbers, which is only 32-bit.
|
|
374
437
|
function __ball_ushr(a: any, b: any): any {
|
|
375
|
-
const unsigned = (
|
|
438
|
+
const unsigned = BigInt.asUintN(64, __to_bigint(a));
|
|
376
439
|
return __i64_wrap(unsigned >> __to_bigint(b));
|
|
377
440
|
}
|
|
441
|
+
// json_encode (dart:convert's jsonEncode) on a bigint-range int64 must not
|
|
442
|
+
// crash -- JSON.stringify throws "Do not know how to serialize a BigInt"
|
|
443
|
+
// without a toJSON. This is NOT proto3-JSON (which quotes int64 as a
|
|
444
|
+
// string) -- Ball's dart:convert-style jsonEncode matches Dart's own
|
|
445
|
+
// dart:convert (a bare, unquoted JSON number) and the C++ self-host's
|
|
446
|
+
// _ball_json_encode (std::to_string(int64_t), also unquoted). JSON.rawJSON
|
|
447
|
+
// embeds the exact decimal digits as a raw number token, avoiding the
|
|
448
|
+
// precision loss Number(this) would introduce for values past 2^53.
|
|
449
|
+
(BigInt.prototype as any).toJSON = function (this: bigint) {
|
|
450
|
+
return (JSON as any).rawJSON(this.toString());
|
|
451
|
+
};
|
|
378
452
|
function __ball_negate(a: any): any {
|
|
379
453
|
if (typeof a === 'bigint') return __i64_wrap(-a);
|
|
380
454
|
if (a instanceof BallDouble) return new BallDouble(-a.value);
|
|
@@ -805,7 +879,7 @@ function __ball_cascade(target: any, ops: any[]): any {
|
|
|
805
879
|
if (!_ballNp.toDouble) _ballNp.toDouble = function () { return Number(this); };
|
|
806
880
|
if (!_ballNp.clamp) _ballNp.clamp = function (lo: any, hi: any) { const n = Number(this); return n < lo ? lo : n > hi ? hi : n; };
|
|
807
881
|
if (!_ballNp.compareTo) _ballNp.compareTo = function (other: any) { const a = Number(this), b = Number(other); return a < b ? -1 : a > b ? 1 : 0; };
|
|
808
|
-
if (!_ballNp.toStringAsFixed) _ballNp.toStringAsFixed = function (digits: any) { return
|
|
882
|
+
if (!_ballNp.toStringAsFixed) _ballNp.toStringAsFixed = function (digits: any) { return __ball_to_fixed(this, digits); };
|
|
809
883
|
if (!_ballNp.remainder) _ballNp.remainder = function (other: any) { return Number(this) % Number(other); };
|
|
810
884
|
|
|
811
885
|
// Object.prototype polyfills — used by the compiled engine when
|
package/dist/preamble.js.map
CHANGED
|
@@ -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
|
|
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA67C5C,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ball-lang/compiler",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.4",
|
|
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
|
@@ -3100,7 +3100,16 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
3100
3100
|
const name = this.resolveVarName(rawName);
|
|
3101
3101
|
// Track this declaration in the current scope.
|
|
3102
3102
|
this.scopeDeclaredVars.add(name);
|
|
3103
|
-
|
|
3103
|
+
// The encoder marks "no initializer" with a reference to the sentinel
|
|
3104
|
+
// name __no_init__ (rather than omitting `value`) — e.g. `int? maybe;`.
|
|
3105
|
+
// Left uncaught, this compiles to `let maybe = __no_init__;`, which
|
|
3106
|
+
// resolves to the runtime preamble's OWN __no_init__ Symbol (used
|
|
3107
|
+
// internally by the compiled self-hosted engine) instead of leaving
|
|
3108
|
+
// `maybe` genuinely undefined, so a later `??=` never fires (#225).
|
|
3109
|
+
// Mirrors the same check already applied to for-loop init bindings
|
|
3110
|
+
// in renderForInit.
|
|
3111
|
+
const isNoInit = stmt.let.value?.reference?.name === "__no_init__";
|
|
3112
|
+
if (stmt.let.value !== undefined && !isNoInit) {
|
|
3104
3113
|
this.writeln(`${kw} ${name} = ${this.expr(stmt.let.value)};`);
|
|
3105
3114
|
} else {
|
|
3106
3115
|
this.writeln(`${kw} ${name};`);
|
|
@@ -4592,7 +4601,17 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
4592
4601
|
} else if (fd.value) {
|
|
4593
4602
|
elems.push(fd.value);
|
|
4594
4603
|
}
|
|
4595
|
-
} else if (
|
|
4604
|
+
} else if (
|
|
4605
|
+
// A `<T>{}` empty-typed-set-literal (e.g. `<int>{}`) carries its
|
|
4606
|
+
// type argument as a field literally named "type_args" (the
|
|
4607
|
+
// encoder's current single-word convention, NOT the older
|
|
4608
|
+
// MessageCreation-level "__type_args__" cosmetic marker checked
|
|
4609
|
+
// elsewhere in this file). Excluding only "__type_args__" left
|
|
4610
|
+
// it falling through to this generic branch and being pushed in
|
|
4611
|
+
// as a bogus set ELEMENT (`<int>{}` -> `new Set(['int'])`
|
|
4612
|
+
// instead of an empty Set, #219).
|
|
4613
|
+
fd.name !== "__type_args__" && fd.name !== "__const__" && fd.name !== "type_args"
|
|
4614
|
+
) {
|
|
4596
4615
|
elems.push(fd.value);
|
|
4597
4616
|
}
|
|
4598
4617
|
}
|
|
@@ -5029,7 +5048,11 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
5029
5048
|
}
|
|
5030
5049
|
// Type ops
|
|
5031
5050
|
case "type_literal": {
|
|
5032
|
-
|
|
5051
|
+
// The encoder's canonical field is "type" (matches the Dart compiler's
|
|
5052
|
+
// _stringFieldValue(f, 'type')) — a type literal's stored value is
|
|
5053
|
+
// Type.toString() (e.g. 'int', 'List<dynamic>'), so this just needs to
|
|
5054
|
+
// evaluate to that string; == and interpolation follow for free.
|
|
5055
|
+
const v = f.get("type") ?? f.get("value") ?? f.get("name");
|
|
5033
5056
|
return v ? this.expr(v) : "null";
|
|
5034
5057
|
}
|
|
5035
5058
|
default: {
|
|
@@ -5099,7 +5122,7 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
5099
5122
|
case "to_string_as_fixed": {
|
|
5100
5123
|
const v = this.expr(fg("value", "arg0")!);
|
|
5101
5124
|
const d = this.expr(fg("digits", "arg1", "right")!);
|
|
5102
|
-
return `(
|
|
5125
|
+
return `__ball_to_fixed(${v}, ${d})`;
|
|
5103
5126
|
}
|
|
5104
5127
|
// num.{round,floor,ceil,truncate}ToDouble() → double (issue #100).
|
|
5105
5128
|
// Wrap in BallDouble so a whole result renders `4.0`, not `4`.
|
|
@@ -6376,8 +6399,14 @@ function dartInitializerToTs(
|
|
|
6376
6399
|
return defaultInitializer(tsType, rawDartType);
|
|
6377
6400
|
}
|
|
6378
6401
|
const s = dartInit.trim();
|
|
6379
|
-
// Already a JS-compatible literal: string, number, bool, null,
|
|
6380
|
-
|
|
6402
|
+
// Already a JS-compatible literal: string, number, bool, null, list.
|
|
6403
|
+
// A non-empty Dart list literal (e.g. `[10, 20, 30]`) is valid JS array
|
|
6404
|
+
// syntax as-is — this used to only match the EMPTY `[]` case, so a field
|
|
6405
|
+
// like `var widgets = [10, 20, 30];` fell through to defaultInitializer
|
|
6406
|
+
// (which has no type info to fall back on when the field's declared type
|
|
6407
|
+
// was inferred rather than explicit) and silently dropped to no
|
|
6408
|
+
// initializer at all, i.e. undefined/null (#220).
|
|
6409
|
+
if (/^(?:null|true|false|'.*'|".*"|-?\d+(?:\.\d+)?|\[[\s\S]*\])$/.test(s)) {
|
|
6381
6410
|
return s;
|
|
6382
6411
|
}
|
|
6383
6412
|
// Dart `{}` can mean either an empty Map or an empty Set depending
|
package/src/preamble.ts
CHANGED
|
@@ -110,7 +110,15 @@ class BallObject extends BallMap {
|
|
|
110
110
|
// (e.g. 42.0 not 42). Used by the compiled Dart engine's _toDouble.
|
|
111
111
|
class BallDouble {
|
|
112
112
|
readonly value: number;
|
|
113
|
-
|
|
113
|
+
// Collapse nested wrapping (new BallDouble(new BallDouble(x))) down to the
|
|
114
|
+
// innermost raw number instead of storing a BallDouble-holding-a-BallDouble.
|
|
115
|
+
// This can legitimately arise now that string_to_double's OWN compiled
|
|
116
|
+
// form already wraps its result (#222): the self-hosted engine's Dart
|
|
117
|
+
// source explicitly does BallDouble(double.parse(s)) to make the parse
|
|
118
|
+
// result double-preserving, which — once double.parse (string_to_double)
|
|
119
|
+
// started wrapping on its own — would otherwise double-wrap. Unwrapping
|
|
120
|
+
// here is idempotent for every other caller, since v is a plain number.
|
|
121
|
+
constructor(v: number) { this.value = v instanceof BallDouble ? v.value : v; }
|
|
114
122
|
valueOf(): number { return this.value; }
|
|
115
123
|
get isNaN(): boolean { return Number.isNaN(this.value); }
|
|
116
124
|
get isFinite(): boolean { return Number.isFinite(this.value); }
|
|
@@ -244,6 +252,13 @@ function __ball_to_string(v: any): string {
|
|
|
244
252
|
}
|
|
245
253
|
return '{' + parts.join(', ') + '}';
|
|
246
254
|
}
|
|
255
|
+
if (v instanceof Set) {
|
|
256
|
+
// A Set is a plain object from typeof's perspective (falls through to
|
|
257
|
+
// the generic branch below, which reads Object.keys — always [] for a
|
|
258
|
+
// Set's internal slots), so every Set printed as the empty "{}" no
|
|
259
|
+
// matter its contents until this dedicated case was added (#219).
|
|
260
|
+
return '{' + [...v].map(__ball_to_string).join(', ') + '}';
|
|
261
|
+
}
|
|
247
262
|
if (typeof v === 'object' && !Array.isArray(v)) {
|
|
248
263
|
// StringBuffer-like objects
|
|
249
264
|
if (v['__buffer__'] && Array.isArray(v['__buffer__'])) {
|
|
@@ -296,10 +311,13 @@ function __ball_to_int(v: any): any {
|
|
|
296
311
|
return t;
|
|
297
312
|
}
|
|
298
313
|
|
|
299
|
-
|
|
314
|
+
// Returns a BallDouble (not a bare number) so a whole-valued result (e.g.
|
|
315
|
+
// double.parse('7.0')) still prints "7.0", not "7" — JS numbers erase the
|
|
316
|
+
// int/double distinction that the wrapper exists to preserve (#67/#222).
|
|
317
|
+
function __ball_parse_double(s: string): BallDouble {
|
|
300
318
|
const n = parseFloat(s);
|
|
301
319
|
if (Number.isNaN(n)) throw new Error('FormatException: ' + s);
|
|
302
|
-
return n;
|
|
320
|
+
return new BallDouble(n);
|
|
303
321
|
}
|
|
304
322
|
|
|
305
323
|
function __ball_double_to_string(n: number): string {
|
|
@@ -307,6 +325,16 @@ function __ball_double_to_string(n: number): string {
|
|
|
307
325
|
return n.toString();
|
|
308
326
|
}
|
|
309
327
|
|
|
328
|
+
// num.toStringAsFixed(digits). JS Number.prototype.toFixed drops the sign of
|
|
329
|
+
// -0 (returns "0.00" not "-0.00"); Dart's toStringAsFixed keeps it, matching
|
|
330
|
+
// the -0 handling __ball_to_string/BallDouble.toString already do.
|
|
331
|
+
function __ball_to_fixed(v: any, digits: any): string {
|
|
332
|
+
const n = Number(v);
|
|
333
|
+
const s = n.toFixed(digits);
|
|
334
|
+
if (n === 0 && 1 / n === -Infinity && !s.startsWith('-')) return '-' + s;
|
|
335
|
+
return s;
|
|
336
|
+
}
|
|
337
|
+
|
|
310
338
|
// Polymorphic concat / merge used for std.list_concat. The encoder emits
|
|
311
339
|
// list_concat both for Dart list concat AND for Map.addAll(...) (encoded as
|
|
312
340
|
// m = list_concat(m, other)). Arrays concat positionally; plain objects
|
|
@@ -350,31 +378,77 @@ function __ball_push_all(target: any, items: any): void {
|
|
|
350
378
|
// demotes back to Number when the result fits in MAX_SAFE_INTEGER.
|
|
351
379
|
const __I64_MAX = 9223372036854775807n;
|
|
352
380
|
const __I64_MIN = -9223372036854775808n;
|
|
353
|
-
const __I64_MOD = 18446744073709551616n;
|
|
354
381
|
function __i64_wrap(v: bigint): any {
|
|
355
|
-
|
|
356
|
-
|
|
382
|
+
// Two's-complement wrap to the signed 64-bit range — BigInt.asIntN(64, v)
|
|
383
|
+
// is the idiomatic builtin for exactly this (equivalent to the old manual
|
|
384
|
+
// modulo-then-resign, verified against it across boundary/overflow cases).
|
|
385
|
+
v = BigInt.asIntN(64, v);
|
|
357
386
|
if (v >= -9007199254740991n && v <= 9007199254740991n) return Number(v);
|
|
358
387
|
return v;
|
|
359
388
|
}
|
|
360
389
|
function __to_bigint(v: any): bigint {
|
|
361
390
|
if (typeof v === 'bigint') return v;
|
|
391
|
+
// Matches the reference Dart engine's _toInt (engine_std.dart), which
|
|
392
|
+
// falls through to 0 for anything that isn't an int/BallInt/double/
|
|
393
|
+
// BallDouble/String/bool -- including null. NaN is deliberately NOT
|
|
394
|
+
// special-cased here: Dart's double.toInt() throws on NaN (via
|
|
395
|
+
// _ballDoubleToInt64), and BigInt(NaN) already throws for the same
|
|
396
|
+
// reason (RangeError: not an integer), so that path already fails loud
|
|
397
|
+
// consistently with the reference engine without any extra handling.
|
|
398
|
+
if (v === null || v === undefined) return 0n;
|
|
362
399
|
if (v instanceof BallDouble) return BigInt(Math.trunc(v.value));
|
|
363
400
|
return BigInt(v);
|
|
364
401
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
402
|
+
// Fast-path guard: true when v is a plain (non-bigint, non-BallDouble)
|
|
403
|
+
// integer within the signed 32-bit range. AND/OR/XOR/NOT never grow a
|
|
404
|
+
// result past its operands' bit width, so when both operands fit in 32
|
|
405
|
+
// bits, JS's native 32-bit bitwise operators give a result numerically
|
|
406
|
+
// IDENTICAL to the full 64-bit BigInt path (sign-extending a 32-bit value
|
|
407
|
+
// to 64 bits before AND/OR/XOR/NOT never changes the low 32 result bits,
|
|
408
|
+
// and — verified across the full boundary range — never changes whether
|
|
409
|
+
// the high bits are the correct sign-extension of them either). Left/right
|
|
410
|
+
// shift are NOT given this fast path: shifting can grow a result past 32
|
|
411
|
+
// bits even when the input operand fits (e.g. large_int << 40), so their
|
|
412
|
+
// overflow behavior isn't safely 32-bit-local the way AND/OR/XOR/NOT is.
|
|
413
|
+
function __fits32(v: any): boolean {
|
|
414
|
+
return typeof v === 'number' && Number.isInteger(v) && v >= -2147483648 && v <= 2147483647;
|
|
415
|
+
}
|
|
416
|
+
function __ball_bitand(a: any, b: any): any {
|
|
417
|
+
if (__fits32(a) && __fits32(b)) return a & b;
|
|
418
|
+
return __i64_wrap(__to_bigint(a) & __to_bigint(b));
|
|
419
|
+
}
|
|
420
|
+
function __ball_bitor(a: any, b: any): any {
|
|
421
|
+
if (__fits32(a) && __fits32(b)) return a | b;
|
|
422
|
+
return __i64_wrap(__to_bigint(a) | __to_bigint(b));
|
|
423
|
+
}
|
|
424
|
+
function __ball_bitxor(a: any, b: any): any {
|
|
425
|
+
if (__fits32(a) && __fits32(b)) return a ^ b;
|
|
426
|
+
return __i64_wrap(__to_bigint(a) ^ __to_bigint(b));
|
|
427
|
+
}
|
|
428
|
+
function __ball_bitnot(a: any): any {
|
|
429
|
+
if (__fits32(a)) return ~a;
|
|
430
|
+
return __i64_wrap(~__to_bigint(a));
|
|
431
|
+
}
|
|
369
432
|
function __ball_shl(a: any, b: any): any { return __i64_wrap(__to_bigint(a) << __to_bigint(b)); }
|
|
370
433
|
function __ball_shr(a: any, b: any): any { return __i64_wrap(__to_bigint(a) >> __to_bigint(b)); }
|
|
371
434
|
// Unsigned/logical shift: reinterpret a as an unsigned 64-bit value (add
|
|
372
435
|
// 2^64 if negative) before shifting, so zeros fill from the left instead of
|
|
373
436
|
// the sign bit — unlike >>> on raw JS numbers, which is only 32-bit.
|
|
374
437
|
function __ball_ushr(a: any, b: any): any {
|
|
375
|
-
const unsigned = (
|
|
438
|
+
const unsigned = BigInt.asUintN(64, __to_bigint(a));
|
|
376
439
|
return __i64_wrap(unsigned >> __to_bigint(b));
|
|
377
440
|
}
|
|
441
|
+
// json_encode (dart:convert's jsonEncode) on a bigint-range int64 must not
|
|
442
|
+
// crash -- JSON.stringify throws "Do not know how to serialize a BigInt"
|
|
443
|
+
// without a toJSON. This is NOT proto3-JSON (which quotes int64 as a
|
|
444
|
+
// string) -- Ball's dart:convert-style jsonEncode matches Dart's own
|
|
445
|
+
// dart:convert (a bare, unquoted JSON number) and the C++ self-host's
|
|
446
|
+
// _ball_json_encode (std::to_string(int64_t), also unquoted). JSON.rawJSON
|
|
447
|
+
// embeds the exact decimal digits as a raw number token, avoiding the
|
|
448
|
+
// precision loss Number(this) would introduce for values past 2^53.
|
|
449
|
+
(BigInt.prototype as any).toJSON = function (this: bigint) {
|
|
450
|
+
return (JSON as any).rawJSON(this.toString());
|
|
451
|
+
};
|
|
378
452
|
function __ball_negate(a: any): any {
|
|
379
453
|
if (typeof a === 'bigint') return __i64_wrap(-a);
|
|
380
454
|
if (a instanceof BallDouble) return new BallDouble(-a.value);
|
|
@@ -805,7 +879,7 @@ function __ball_cascade(target: any, ops: any[]): any {
|
|
|
805
879
|
if (!_ballNp.toDouble) _ballNp.toDouble = function () { return Number(this); };
|
|
806
880
|
if (!_ballNp.clamp) _ballNp.clamp = function (lo: any, hi: any) { const n = Number(this); return n < lo ? lo : n > hi ? hi : n; };
|
|
807
881
|
if (!_ballNp.compareTo) _ballNp.compareTo = function (other: any) { const a = Number(this), b = Number(other); return a < b ? -1 : a > b ? 1 : 0; };
|
|
808
|
-
if (!_ballNp.toStringAsFixed) _ballNp.toStringAsFixed = function (digits: any) { return
|
|
882
|
+
if (!_ballNp.toStringAsFixed) _ballNp.toStringAsFixed = function (digits: any) { return __ball_to_fixed(this, digits); };
|
|
809
883
|
if (!_ballNp.remainder) _ballNp.remainder = function (other: any) { return Number(this) % Number(other); };
|
|
810
884
|
|
|
811
885
|
// Object.prototype polyfills — used by the compiled engine when
|