@ball-lang/engine 1.63.1 → 1.64.0

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.
@@ -1474,6 +1474,36 @@ function __ball_is_type(value: any, typeStr: string): boolean {
1474
1474
  }
1475
1475
  }
1476
1476
 
1477
+ // std.type_of (#489) - the string form of the very discrimination
1478
+ // __ball_is_type performs, i.e. the canonical BASE type name with generic
1479
+ // type arguments dropped and any module prefix stripped. Bare JS typeof
1480
+ // cannot be used: typeof [] is 'object', not 'list', and it cannot tell an
1481
+ // int from a double. Must agree with the Dart reference engine's _typeNameOf.
1482
+ function __ball_type_of(value: any): string {
1483
+ if (value == null) return 'Null';
1484
+ if (typeof value === 'boolean') return 'bool';
1485
+ if (value instanceof BallDouble) return 'double';
1486
+ if (typeof value === 'number') return Number.isInteger(value) ? 'int' : 'double';
1487
+ if (typeof value === 'string') return 'String';
1488
+ if (Array.isArray(value)) return 'List';
1489
+ if (value instanceof Set) return 'Set';
1490
+ if (typeof value === 'function') return 'Function';
1491
+ if (typeof value === 'object') {
1492
+ const tag = value.__type__;
1493
+ if (typeof tag === 'string' && tag.length > 0) {
1494
+ const colon = tag.indexOf(':');
1495
+ return colon >= 0 ? tag.slice(colon + 1) : tag;
1496
+ }
1497
+ // A compiled user class is a real TS class; a Dart Map is a plain object.
1498
+ const ctor = value.constructor?.name;
1499
+ if (typeof ctor === 'string' && ctor.length > 0 && ctor !== 'Object') {
1500
+ return ctor;
1501
+ }
1502
+ return 'Map';
1503
+ }
1504
+ return typeof value;
1505
+ }
1506
+
1477
1507
  // Minimal DateTime / Duration / Future polyfills used by std_time and
1478
1508
  // the round-tripped engine's sleep_ms helper. Wide enough for the
1479
1509
  // conformance suite, narrow enough to stay out of users' way.
@@ -1662,6 +1692,12 @@ export class BallEngine {
1662
1692
  this.maxProgramSizeBytes = maxProgramSizeBytes;
1663
1693
  this.sandbox = sandbox;
1664
1694
  this.moduleHandlers = moduleHandlers;
1695
+ this.stdout = stdout ?? print;
1696
+ this._resolver = resolver;
1697
+ this.stderr = stderr ?? ((s) => io.stderr.writeln(s));
1698
+ this._envGet = envGet ?? ((name) => io.Platform.environment[name] ?? '');
1699
+ this._args = args ?? [];
1700
+ this.moduleHandlers = moduleHandlers ?? [StdModuleHandler()];
1665
1701
  this._validateProgramLimits();
1666
1702
  if (enableProfiling) {
1667
1703
  this._callCounts = {};
@@ -2327,6 +2363,7 @@ export class BallEngine {
2327
2363
  }
2328
2364
  }
2329
2365
  this._initFieldDefaults(typeName, instanceFields);
2366
+ this._applyConstructorInitializers(func, instanceFields, resolvedParams, true);
2330
2367
  let superclass = this._getMetaString(typeDef, 'superclass');
2331
2368
  let superObject;
2332
2369
  if ((!__ball_eq(superclass, null) && !(superclass.length === 0))) {
@@ -2391,17 +2428,21 @@ export class BallEngine {
2391
2428
  targetFields[name] = null;
2392
2429
  }
2393
2430
  } else {
2394
- if (__ball_eq(valStr, 'true')) {
2395
- targetFields[name] = true;
2431
+ if ((__ball_ge(valStr.length, 2) && ((valStr.startsWith('\'') && valStr.endsWith('\'')) || (valStr.startsWith('"') && valStr.endsWith('"'))))) {
2432
+ targetFields[name] = valStr.substring(1, __ball_sub(valStr.length, 1));
2396
2433
  } else {
2397
- if (__ball_eq(valStr, 'false')) {
2398
- targetFields[name] = false;
2434
+ if (__ball_eq(valStr, 'true')) {
2435
+ targetFields[name] = true;
2399
2436
  } else {
2400
- if (!__ball_eq(num.tryParse(valStr), null)) {
2401
- let numVal = num.parse(valStr);
2402
- targetFields[name] = (valStr.includes('.') ? new BallDouble(new BallDouble(Number(numVal))) : __ball_to_int(numVal));
2437
+ if (__ball_eq(valStr, 'false')) {
2438
+ targetFields[name] = false;
2403
2439
  } else {
2404
- targetFields[name] = (__ball_index(resolvedParams, valStr) ?? valStr);
2440
+ if (!__ball_eq(num.tryParse(valStr), null)) {
2441
+ let numVal = num.parse(valStr);
2442
+ targetFields[name] = (valStr.includes('.') ? new BallDouble(new BallDouble(Number(numVal))) : __ball_to_int(numVal));
2443
+ } else {
2444
+ targetFields[name] = (__ball_index(resolvedParams, valStr) ?? valStr);
2445
+ }
2405
2446
  }
2406
2447
  }
2407
2448
  }
@@ -4264,6 +4305,25 @@ export class BallEngine {
4264
4305
  }
4265
4306
  }
4266
4307
 
4308
+ _isBareSelfConstruction(msg: any): any {
4309
+ const input = msg;
4310
+ let ctorEntry = this._lookupConstructor(msg.typeName);
4311
+ let params = ((!__ball_eq(ctorEntry, null) && hasMetadata(ctorEntry.func)) ? this._extractParams(ctorEntry.func.metadata) : []);
4312
+ for (const pair of msg.fields) {
4313
+ let name = pair.name;
4314
+ if (((__ball_eq(name, '__type_args__') || __ball_eq(name, 'type_args')) || __ball_eq(name, '__const__'))) {
4315
+ continue;
4316
+ }
4317
+ if (new RegExp('^arg\\d+$').hasMatch(name)) {
4318
+ return false;
4319
+ }
4320
+ if (params.includes(name)) {
4321
+ return false;
4322
+ }
4323
+ }
4324
+ return true;
4325
+ }
4326
+
4267
4327
  async _evalMessageCreation(msg: any, scope: any): Promise<any> {
4268
4328
  let fields = {};
4269
4329
  for (const pair of msg.fields) {
@@ -4288,7 +4348,7 @@ export class BallEngine {
4288
4348
  let self = scope.lookup('self');
4289
4349
  let constructorType = scope.lookup('__constructor_type__');
4290
4350
  let selfMap = this._asMap(self);
4291
- if ((!__ball_eq(selfMap, null) && __ball_eq(constructorType, msg.typeName))) {
4351
+ if (((!__ball_eq(selfMap, null) && __ball_eq(constructorType, msg.typeName)) && this._isBareSelfConstruction(msg))) {
4292
4352
  return self;
4293
4353
  }
4294
4354
  }
@@ -4334,7 +4394,7 @@ export class BallEngine {
4334
4394
  }
4335
4395
  }
4336
4396
  }
4337
- if (((!__ball_eq(ctorEntry, null) && hasMetadata(ctorEntry.func)) && !hasBody(ctorEntry.func))) {
4397
+ if ((!__ball_eq(ctorEntry, null) && hasMetadata(ctorEntry.func))) {
4338
4398
  this._applyConstructorInitializers(ctorEntry.func, instanceFields, resolvedParams, true);
4339
4399
  }
4340
4400
  let superclass = this._getMetaString(typeDef, 'superclass');
@@ -4388,7 +4448,7 @@ export class BallEngine {
4388
4448
  }
4389
4449
  return instance;
4390
4450
  } else {
4391
- if (((scope.has('self') && scope.has('__constructor_type__')) && __ball_eq(scope.lookup('__constructor_type__'), msg.typeName))) {
4451
+ if ((((scope.has('self') && scope.has('__constructor_type__')) && __ball_eq(scope.lookup('__constructor_type__'), msg.typeName)) && this._isBareSelfConstruction(msg))) {
4392
4452
  let self = scope.lookup('self');
4393
4453
  if (!__ball_eq(this._asMap(self), null)) {
4394
4454
  return self;
@@ -5460,7 +5520,7 @@ export class BallEngine {
5460
5520
  let eBare = (__ball_ge(eColonIdx, 0) ? eType.substring(__ball_add(eColonIdx, 1)) : eType);
5461
5521
  matches = (__ball_eq(eType, catchType) || __ball_eq(eBare, catchType));
5462
5522
  } else {
5463
- matches = __ball_eq(__ball_to_string(e['runtimeType']), catchType);
5523
+ matches = __ball_eq(__ball_type_of(e), catchType);
5464
5524
  }
5465
5525
  }
5466
5526
  if (!matches) {
@@ -7276,7 +7336,7 @@ export class BallEngine {
7276
7336
  }), ['null_aware_access']: this._stdNullAwareAccess.bind(this), ['null_aware_call']: this._stdNullAwareCall.bind(this), ['if']: this._stdIf.bind(this), ['is']: this._stdTypeCheck.bind(this), ['is_not']: ((i) => {
7277
7337
  const input = i;
7278
7338
  return !this._stdTypeCheck(i);
7279
- }), ['as']: this._extractUnaryArg.bind(this), ['index']: this._stdIndex.bind(this), ['cascade']: this._stdCascade.bind(this), ['null_aware_cascade']: this._stdNullAwareCascade.bind(this), ['spread']: this._extractUnaryArg.bind(this), ['null_spread']: this._extractUnaryArg.bind(this), ['invoke']: this._stdInvoke.bind(this), ['tear_off']: ((i) => {
7339
+ }), ['as']: this._extractUnaryArg.bind(this), ['type_of']: this._stdTypeOf.bind(this), ['index']: this._stdIndex.bind(this), ['cascade']: this._stdCascade.bind(this), ['null_aware_cascade']: this._stdNullAwareCascade.bind(this), ['spread']: this._extractUnaryArg.bind(this), ['null_spread']: this._extractUnaryArg.bind(this), ['invoke']: this._stdInvoke.bind(this), ['tear_off']: ((i) => {
7280
7340
  const input = i;
7281
7341
  let m = this._stdAsMap(i);
7282
7342
  if (!__ball_eq(m, null)) {
@@ -8855,6 +8915,52 @@ export class BallEngine {
8855
8915
  return this._typeMatches(value, type);
8856
8916
  }
8857
8917
 
8918
+ _stdTypeOf(input: any): any {
8919
+ let m = this._stdAsMap(input);
8920
+ if (__ball_eq(m, null)) {
8921
+ throw new BallRuntimeError('std.type_of: expected an input message');
8922
+ }
8923
+ return this._typeNameOf(__ball_index(m, 'value'));
8924
+ }
8925
+
8926
+ _typeNameOf(value: any): any {
8927
+ const input = value;
8928
+ if ((__ball_eq(value, null) || (value == null))) {
8929
+ return 'Null';
8930
+ }
8931
+ if (_ballIsBool(value)) {
8932
+ return 'bool';
8933
+ }
8934
+ if (_ballIsInt(value)) {
8935
+ return 'int';
8936
+ }
8937
+ if (_ballIsDouble(value)) {
8938
+ return 'double';
8939
+ }
8940
+ if (_ballIsString(value)) {
8941
+ return 'String';
8942
+ }
8943
+ if (_ballIsList(value)) {
8944
+ return 'List';
8945
+ }
8946
+ if (this._isBallSet(value)) {
8947
+ return 'Set';
8948
+ }
8949
+ if (((typeof value === 'function') || (typeof value === 'function'))) {
8950
+ return 'Function';
8951
+ }
8952
+ let objMap = this._stdAsMap(value);
8953
+ if (!__ball_eq(objMap, null)) {
8954
+ let tag = __ball_index(objMap, '__type__');
8955
+ if (((typeof tag === 'string') && !(tag.length === 0))) {
8956
+ let colonIdx = tag.indexOf(':');
8957
+ return (__ball_ge(colonIdx, 0) ? tag.substring(__ball_add(colonIdx, 1)) : tag);
8958
+ }
8959
+ return 'Map';
8960
+ }
8961
+ return __ball_to_string(value.runtimeType);
8962
+ }
8963
+
8858
8964
  _typeMatches(value: any, type: any): any {
8859
8965
  let genericMatch = new RegExp('^(\\w+)<(.+)>$').firstMatch(type);
8860
8966
  if (!__ball_eq(genericMatch, null)) {
@@ -10229,6 +10335,7 @@ export class StdModuleHandler extends BallModuleHandler {
10229
10335
 
10230
10336
  constructor() {
10231
10337
  super();
10338
+ this._allowlist = null;
10232
10339
  }
10233
10340
 
10234
10341
  get registeredFunctions(): any {
@@ -976,8 +976,15 @@ export function createEngineSetup(mod: EngineModule) {
976
976
  _r('string_to_upper_case', (i: any) => { const m = _m(i); return String(m['value'] ?? m['string'] ?? '').toUpperCase(); });
977
977
  _r('string_to_lower_case', (i: any) => { const m = _m(i); return String(m['value'] ?? m['string'] ?? '').toLowerCase(); });
978
978
  _r('string_trim', (i: any) => { const m = _m(i); return String(m['value'] ?? m['string'] ?? '').trim(); });
979
- _r('string_starts_with', (i: any) => { const m = _m(i); return String(m['value'] ?? m['string'] ?? '').startsWith(String(m['prefix'] ?? m['pattern'] ?? '')); });
980
- _r('string_ends_with', (i: any) => { const m = _m(i); return String(m['value'] ?? m['string'] ?? '').endsWith(String(m['suffix'] ?? m['pattern'] ?? '')); });
979
+ // The encoder emits both of these as a canonical `BinaryInput`
980
+ // (`left`/`right`), like every other two-operand string op. Reading only
981
+ // `value`/`prefix` made BOTH lookups miss and collapse to
982
+ // `''.startsWith('')` / `''.endsWith('')` -- ALWAYS TRUE, silently. The
983
+ // corpus never caught it because every fixture using them asserted a TRUE
984
+ // answer (260/382/416); 260_string_functions now pins the false cases too.
985
+ // Legacy `value`/`prefix`/`suffix`/`pattern` spellings stay as fallbacks.
986
+ _r('string_starts_with', (i: any) => { const m = _m(i); return String(m['left'] ?? m['value'] ?? m['string'] ?? '').startsWith(String(m['right'] ?? m['prefix'] ?? m['pattern'] ?? '')); });
987
+ _r('string_ends_with', (i: any) => { const m = _m(i); return String(m['left'] ?? m['value'] ?? m['string'] ?? '').endsWith(String(m['right'] ?? m['suffix'] ?? m['pattern'] ?? '')); });
981
988
  // `m['width'] ?? m['length'] ?? 0` has the same Object.prototype '.length'
982
989
  // getter hazard as `_countOf` above: plain bracket access on 'length'
983
990
  // never falls through to the `0` default (it returns the input map's own