zuzu-js 0.4.0 → 0.6.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.
package/lib/runtime.js CHANGED
@@ -40,6 +40,7 @@ const {
40
40
  resolveWeakValue,
41
41
  retainValue,
42
42
  releaseValue,
43
+ strongReferenceCount,
43
44
  retainCollectionValue,
44
45
  releaseCollectionValue,
45
46
  releaseCollectionValues,
@@ -121,6 +122,12 @@ function installHostCollectionMethods() {
121
122
  this.add( makeWeakValue( value ) );
122
123
  return this;
123
124
  } );
125
+ define( Set.prototype, 'push_weak', function _pushWeak( ...values ) {
126
+ for ( const value of values ) {
127
+ this.add( makeWeakValue( value ) );
128
+ }
129
+ return this;
130
+ } );
124
131
  define( Set.prototype, 'copy', function _copy() {
125
132
  return makeSet( this );
126
133
  } );
@@ -463,8 +470,12 @@ async function runSwitchAsync( value, comparator, cases, defaultBody ) {
463
470
  switch ( comparator ) {
464
471
  case 'eq':
465
472
  return stringCompare( left, right ) === 0;
473
+ case 'eqi':
474
+ return stringCompare( left, right, { insensitive: true } ) === 0;
466
475
  case 'ne':
467
476
  return stringCompare( left, right ) !== 0;
477
+ case 'nei':
478
+ return stringCompare( left, right, { insensitive: true } ) !== 0;
468
479
  case '=':
469
480
  return numericEqual( zuzuToNumber( left ), zuzuToNumber( right ) );
470
481
  case '==':
@@ -474,7 +485,18 @@ async function runSwitchAsync( value, comparator, cases, defaultBody ) {
474
485
  };
475
486
  let runNext = false;
476
487
  for ( const section of cases ) {
477
- const matched = section.values.some( (item) => cmp( value, item ) );
488
+ let matched = false;
489
+ if ( Array.isArray( section.tests ) ) {
490
+ for ( const test of section.tests ) {
491
+ if ( zuzuTruthy( await taskRuntime.awaitValue( test( value ) ) ) ) {
492
+ matched = true;
493
+ break;
494
+ }
495
+ }
496
+ }
497
+ else {
498
+ matched = section.values.some( (item) => cmp( value, item ) );
499
+ }
478
500
  if ( matched || runNext ) {
479
501
  const result = await taskRuntime.awaitValue( section.body() );
480
502
  runNext = result === true;
@@ -739,9 +761,7 @@ function isPlainObjectLike( value ) {
739
761
  }
740
762
 
741
763
  function isDictMethodReceiver( value ) {
742
- return value
743
- && typeof value === 'object'
744
- && !Array.isArray( value )
764
+ return isPlainObjectLike( value )
745
765
  && !isPairListLike( value )
746
766
  && !isSetLike( value )
747
767
  && !isBagLike( value );
@@ -764,18 +784,371 @@ function normalizeDictKey( key ) {
764
784
  return String( key );
765
785
  }
766
786
 
787
+ function arraySliceBounds( length, start, end = length ) {
788
+ let from = Number( start ?? 0 );
789
+ let to = Number( end ?? length );
790
+ from = Number.isFinite( from ) ? Math.trunc( from ) : 0;
791
+ to = Number.isFinite( to ) ? Math.trunc( to ) : length;
792
+ if ( from < 0 ) {
793
+ from += length;
794
+ }
795
+ if ( to < 0 ) {
796
+ to += length;
797
+ }
798
+ from = Math.max( 0, Math.min( length, from ) );
799
+ to = Math.max( 0, Math.min( length, to ) );
800
+ return [ Math.min( from, to ), Math.max( from, to ) ];
801
+ }
802
+
803
+ function arrayMethodArity( name, actual, min, max = min ) {
804
+ const count = actual - 1;
805
+ if ( count < min || count > max ) {
806
+ throw new Error(
807
+ min === max
808
+ ? `${name} expects ${min} argument${min === 1 ? '' : 's'}`
809
+ : `${name} expects between ${min} and ${max} arguments`
810
+ );
811
+ }
812
+ }
813
+
814
+ function arrayIndex( length, idx ) {
815
+ let index = Number( idx );
816
+ index = Number.isFinite( index ) ? Math.trunc( index ) : 0;
817
+ if ( index < 0 ) {
818
+ index += length;
819
+ }
820
+ return index;
821
+ }
822
+
823
+ function shuffledArrayValues( self ) {
824
+ const out = self.slice();
825
+ for ( let idx = out.length - 1; idx > 0; idx-- ) {
826
+ const swapIdx = Math.floor( Math.random() * ( idx + 1 ) );
827
+ [ out[idx], out[swapIdx] ] = [ out[swapIdx], out[idx] ];
828
+ }
829
+ return out;
830
+ }
831
+
832
+ function arrayJoin( self, separator, fallback, hasFallback ) {
833
+ const sep = operatorString( separator );
834
+ let fallbackString;
835
+ const out = [];
836
+ for ( const value of self ) {
837
+ try {
838
+ out.push( operatorString( value ) );
839
+ }
840
+ catch ( err ) {
841
+ if ( !hasFallback ) {
842
+ throw err;
843
+ }
844
+ if ( typeof fallback === 'function' ) {
845
+ out.push( operatorString( fallback( value ) ) );
846
+ }
847
+ else {
848
+ if ( fallbackString === undefined ) {
849
+ fallbackString = operatorString( fallback );
850
+ }
851
+ out.push( fallbackString );
852
+ }
853
+ }
854
+ }
855
+ return out.join( sep );
856
+ }
857
+
858
+ const ARRAY_METHODS = Object.freeze( {
859
+ append( self, ...values ) {
860
+ self.push( ...values.map( (value) => retainCollectionValue( self, value ) ) );
861
+ return self;
862
+ },
863
+ push( self, ...values ) {
864
+ return ARRAY_METHODS.append( self, ...values );
865
+ },
866
+ add( self, ...values ) {
867
+ return ARRAY_METHODS.append( self, ...values );
868
+ },
869
+ push_weak( self, ...values ) {
870
+ self.push( ...values.map( makeWeakValue ) );
871
+ return self;
872
+ },
873
+ pop( self ) {
874
+ return self.length ? self.pop() : null;
875
+ },
876
+ prepend( self, ...values ) {
877
+ self.unshift( ...values.map( (value) => retainCollectionValue( self, value ) ) );
878
+ return self;
879
+ },
880
+ unshift( self, ...values ) {
881
+ return ARRAY_METHODS.prepend( self, ...values );
882
+ },
883
+ unshift_weak( self, ...values ) {
884
+ self.unshift( ...values.map( makeWeakValue ) );
885
+ return self;
886
+ },
887
+ shift( self ) {
888
+ return self.length ? self.shift() : null;
889
+ },
890
+ length( self ) {
891
+ return self.length;
892
+ },
893
+ count( self ) {
894
+ return self.length;
895
+ },
896
+ empty( self ) {
897
+ return self.length === 0 ? 1 : 0;
898
+ },
899
+ is_empty( self ) {
900
+ return ARRAY_METHODS.empty( self );
901
+ },
902
+ copy( self ) {
903
+ return makeArray( self );
904
+ },
905
+ to_Array( self ) {
906
+ return makeArray( self );
907
+ },
908
+ get( self, idx, fallback = null ) {
909
+ arrayMethodArity( 'Array.get', arguments.length, 1, 2 );
910
+ const index = arrayIndex( self.length, idx );
911
+ return index >= 0 && index < self.length ? self[index] : fallback;
912
+ },
913
+ set( self, idx, value ) {
914
+ arrayMethodArity( 'Array.set', arguments.length, 2 );
915
+ const index = arrayIndex( self.length, idx );
916
+ if ( index < 0 ) {
917
+ throw new Error( 'Array index is out of range' );
918
+ }
919
+ releaseCollectionValue( self, self[index] );
920
+ self[index] = retainCollectionValue( self, value );
921
+ return self;
922
+ },
923
+ set_weak( self, idx, value ) {
924
+ arrayMethodArity( 'Array.set_weak', arguments.length, 2 );
925
+ const index = arrayIndex( self.length, idx );
926
+ if ( index < 0 ) {
927
+ throw new Error( 'Array index is out of range' );
928
+ }
929
+ releaseCollectionValue( self, self[index] );
930
+ self[index] = makeWeakValue( value );
931
+ return self;
932
+ },
933
+ clear( self ) {
934
+ releaseCollectionValues( self );
935
+ self.splice( 0, self.length );
936
+ return self;
937
+ },
938
+ join( self, separator, fallback ) {
939
+ return arrayJoin( self, separator, fallback, arguments.length > 2 );
940
+ },
941
+ slice( self, start, end ) {
942
+ const [ from, to ] = arraySliceBounds( self.length, start, end );
943
+ return makeArray( self.slice( from, to ) );
944
+ },
945
+ head( self, n = 1 ) {
946
+ arrayMethodArity( 'Array.head', arguments.length, 0, 1 );
947
+ return makeArray( self.slice( 0, Math.max( 0, Number( n ) || 0 ) ) );
948
+ },
949
+ tail( self, n = 1 ) {
950
+ arrayMethodArity( 'Array.tail', arguments.length, 0, 1 );
951
+ const count = Math.max( 0, Number( n ) || 0 );
952
+ return makeArray( count === 0 ? [] : self.slice( -count ) );
953
+ },
954
+ to_Set( self ) {
955
+ return makeSet( self );
956
+ },
957
+ to_Bag( self ) {
958
+ return makeBag( self );
959
+ },
960
+ to_Iterator( self ) {
961
+ return self[Symbol.iterator]();
962
+ },
963
+ sort( self, fn ) {
964
+ const out = self.slice();
965
+ out.sort( ( left, right ) => Number( fn( left, right ) ) || 0 );
966
+ return makeArray( out );
967
+ },
968
+ sortstr( self ) {
969
+ return makeArray( self.slice().sort( stringSortComparator ) );
970
+ },
971
+ sortnum( self ) {
972
+ return makeArray(
973
+ self
974
+ .map( (item) => Number( item ) )
975
+ .sort( (a, b) => a - b )
976
+ );
977
+ },
978
+ reverse( self ) {
979
+ return makeArray( self.slice().reverse() );
980
+ },
981
+ sum( self ) {
982
+ return self.reduce( (a, b) => Number( a ) + Number( b ), 0 );
983
+ },
984
+ product( self ) {
985
+ return self.reduce( (a, b) => Number( a ) * Number( b ), 1 );
986
+ },
987
+ shuffle( self ) {
988
+ arrayMethodArity( 'Array.shuffle', arguments.length, 0 );
989
+ return makeArray( shuffledArrayValues( self ) );
990
+ },
991
+ sample( self, n = 1 ) {
992
+ arrayMethodArity( 'Array.sample', arguments.length, 0, 1 );
993
+ return makeArray(
994
+ shuffledArrayValues( self ).slice( 0, Math.max( 0, Number( n ) || 0 ) )
995
+ );
996
+ },
997
+ contains( self, value ) {
998
+ return self.includes( value ) ? 1 : 0;
999
+ },
1000
+ map( self, fn ) {
1001
+ arrayMethodArity( 'Array.map', arguments.length, 1 );
1002
+ return makeArray( self.map( (value) => fn( value ) ) );
1003
+ },
1004
+ grep( self, fn ) {
1005
+ arrayMethodArity( 'Array.grep', arguments.length, 1 );
1006
+ return makeArray( self.filter( (value) => fn( value ) ) );
1007
+ },
1008
+ any( self, fn ) {
1009
+ arrayMethodArity( 'Array.any', arguments.length, 1 );
1010
+ return self.some( (value) => fn( value ) ) ? 1 : 0;
1011
+ },
1012
+ all( self, fn ) {
1013
+ arrayMethodArity( 'Array.all', arguments.length, 1 );
1014
+ return self.every( (value) => fn( value ) ) ? 1 : 0;
1015
+ },
1016
+ first( self, fn ) {
1017
+ arrayMethodArity( 'Array.first', arguments.length, 1 );
1018
+ for ( const value of self ) {
1019
+ if ( fn( value ) ) {
1020
+ return value;
1021
+ }
1022
+ }
1023
+ return null;
1024
+ },
1025
+ remove( self, fn ) {
1026
+ for ( let idx = self.length - 1; idx >= 0; idx-- ) {
1027
+ if ( fn( self[idx] ) ) {
1028
+ releaseCollectionValue( self, self[idx] );
1029
+ self.splice( idx, 1 );
1030
+ }
1031
+ }
1032
+ return self;
1033
+ },
1034
+ first_index( self, fn ) {
1035
+ for ( let idx = 0; idx < self.length; idx++ ) {
1036
+ if ( fn( self[idx] ) ) {
1037
+ return idx;
1038
+ }
1039
+ }
1040
+ return -1;
1041
+ },
1042
+ for_each_value( self, fn ) {
1043
+ for ( const value of self ) {
1044
+ fn( value );
1045
+ }
1046
+ return self;
1047
+ },
1048
+ reduce( self, fn ) {
1049
+ if ( self.length === 0 ) {
1050
+ return null;
1051
+ }
1052
+ let acc = self[0];
1053
+ for ( let idx = 1; idx < self.length; idx++ ) {
1054
+ acc = fn( acc, self[idx] );
1055
+ }
1056
+ return acc;
1057
+ },
1058
+ reductions( self, fn ) {
1059
+ if ( self.length === 0 ) {
1060
+ return makeArray( [] );
1061
+ }
1062
+ const out = [ self[0] ];
1063
+ for ( let idx = 1; idx < self.length; idx++ ) {
1064
+ out.push( fn( out[out.length - 1], self[idx] ) );
1065
+ }
1066
+ return makeArray( out );
1067
+ },
1068
+ } );
1069
+
1070
+ const ARRAY_ZERO_ARG_METHODS = new Set( [
1071
+ 'copy',
1072
+ 'count',
1073
+ 'empty',
1074
+ 'is_empty',
1075
+ 'length',
1076
+ 'pop',
1077
+ 'reverse',
1078
+ 'shift',
1079
+ 'shuffle',
1080
+ 'sortnum',
1081
+ 'sortstr',
1082
+ 'to_Array',
1083
+ 'to_Bag',
1084
+ 'to_Iterator',
1085
+ 'to_Set',
1086
+ ] );
1087
+
1088
+ function getArrayMethod( object, property ) {
1089
+ if ( typeof property === 'symbol' ) {
1090
+ return null;
1091
+ }
1092
+ const name = String( property );
1093
+ if ( !Array.isArray( object ) || !Object.prototype.hasOwnProperty.call( ARRAY_METHODS, name ) ) {
1094
+ return null;
1095
+ }
1096
+ return function zuzuArrayMethod( ...args ) {
1097
+ return ARRAY_METHODS[name]( object, ...args );
1098
+ };
1099
+ }
1100
+
1101
+ const SET_METHODS = Object.freeze( {
1102
+ add_weak( self, ...values ) {
1103
+ for ( const value of values ) {
1104
+ self.add( makeWeakValue( value ) );
1105
+ }
1106
+ return self;
1107
+ },
1108
+ push_weak( self, ...values ) {
1109
+ return SET_METHODS.add_weak( self, ...values );
1110
+ },
1111
+ clear( self ) {
1112
+ releaseCollectionValues( self );
1113
+ Set.prototype.clear.call( self );
1114
+ return self;
1115
+ },
1116
+ } );
1117
+
1118
+ const SET_ZERO_ARG_METHODS = new Set( [ 'clear' ] );
1119
+
1120
+ function getSetMethod( object, property ) {
1121
+ if ( typeof property === 'symbol' ) {
1122
+ return null;
1123
+ }
1124
+ const name = String( property );
1125
+ if (
1126
+ !isSetLike( object )
1127
+ || !Object.prototype.hasOwnProperty.call( SET_METHODS, name )
1128
+ ) {
1129
+ return null;
1130
+ }
1131
+ return function zuzuSetMethod( ...args ) {
1132
+ return SET_METHODS[name]( object, ...args );
1133
+ };
1134
+ }
1135
+
1136
+ function dictSortedKeys( self ) {
1137
+ return isDictMethodReceiver( self ) ? Object.keys( self ).sort() : [];
1138
+ }
1139
+
767
1140
  const DICT_METHODS = Object.freeze( {
768
1141
  length( self ) {
769
1142
  return isDictMethodReceiver( self ) ? Object.keys( self ).length : 0;
770
1143
  },
771
1144
  keys( self ) {
772
- return isDictMethodReceiver( self ) ? Object.keys( self ).sort() : [];
1145
+ return makeSet( dictSortedKeys( self ) );
773
1146
  },
774
1147
  values( self ) {
775
1148
  if ( !isDictMethodReceiver( self ) ) {
776
- return [];
1149
+ return makeBag( [] );
777
1150
  }
778
- return DICT_METHODS.keys( self ).map( (key) => resolveWeakValue( self[key] ) );
1151
+ return makeBag( dictSortedKeys( self ).map( (key) => resolveWeakValue( self[key] ) ) );
779
1152
  },
780
1153
  copy( self ) {
781
1154
  if ( !isDictMethodReceiver( self ) ) {
@@ -789,11 +1162,11 @@ const DICT_METHODS = Object.freeze( {
789
1162
  },
790
1163
  enumerate( self ) {
791
1164
  if ( !isDictMethodReceiver( self ) ) {
792
- return [];
1165
+ return makeBag( [] );
793
1166
  }
794
- return DICT_METHODS.keys( self ).map(
1167
+ return makeBag( dictSortedKeys( self ).map(
795
1168
  (key) => new Pair( { pair: [ key, resolveWeakValue( self[key] ) ] } )
796
- );
1169
+ ) );
797
1170
  },
798
1171
  has( self, key ) {
799
1172
  return isDictMethodReceiver( self )
@@ -847,13 +1220,13 @@ const DICT_METHODS = Object.freeze( {
847
1220
  return [];
848
1221
  }
849
1222
  const out = [];
850
- for ( const key of DICT_METHODS.keys( self ) ) {
1223
+ for ( const key of dictSortedKeys( self ) ) {
851
1224
  out.push( key, resolveWeakValue( self[key] ) );
852
1225
  }
853
1226
  return out;
854
1227
  },
855
1228
  sorted_keys( self ) {
856
- return DICT_METHODS.keys( self );
1229
+ return dictSortedKeys( self );
857
1230
  },
858
1231
  remove( self, key ) {
859
1232
  if ( !isDictMethodReceiver( self ) ) {
@@ -886,26 +1259,31 @@ const DICT_METHODS = Object.freeze( {
886
1259
  return DICT_METHODS.empty( self );
887
1260
  },
888
1261
  to_Array( self ) {
889
- return DICT_METHODS.enumerate( self );
1262
+ if ( !isDictMethodReceiver( self ) ) {
1263
+ return [];
1264
+ }
1265
+ return makeArray( dictSortedKeys( self ).map(
1266
+ (key) => new Pair( { pair: [ key, resolveWeakValue( self[key] ) ] } )
1267
+ ) );
890
1268
  },
891
1269
  to_Iterator( self ) {
892
- return DICT_METHODS.keys( self )[Symbol.iterator]();
1270
+ return dictSortedKeys( self )[Symbol.iterator]();
893
1271
  },
894
1272
  for_each_key( self, fn ) {
895
- for ( const key of DICT_METHODS.keys( self ) ) {
1273
+ for ( const key of dictSortedKeys( self ) ) {
896
1274
  fn( key );
897
1275
  }
898
1276
  return self;
899
1277
  },
900
1278
  for_each_value( self, fn ) {
901
- for ( const value of DICT_METHODS.values( self ) ) {
902
- fn( value );
1279
+ for ( const key of dictSortedKeys( self ) ) {
1280
+ fn( resolveWeakValue( self[key] ) );
903
1281
  }
904
1282
  return self;
905
1283
  },
906
1284
  for_each_pair( self, fn ) {
907
- for ( const pair of DICT_METHODS.enumerate( self ) ) {
908
- fn( pair );
1285
+ for ( const key of dictSortedKeys( self ) ) {
1286
+ fn( new Pair( { pair: [ key, resolveWeakValue( self[key] ) ] } ) );
909
1287
  }
910
1288
  return self;
911
1289
  },
@@ -941,6 +1319,12 @@ function getDictMethod( object, property ) {
941
1319
  return null;
942
1320
  }
943
1321
  const name = String( property );
1322
+ for ( let proto = Object.getPrototypeOf( object ); proto; proto = Object.getPrototypeOf( proto ) ) {
1323
+ const descriptor = Object.getOwnPropertyDescriptor( proto, name );
1324
+ if ( descriptor && typeof descriptor.value === 'function' ) {
1325
+ return null;
1326
+ }
1327
+ }
944
1328
  if (
945
1329
  !isDictMethodReceiver( object )
946
1330
  || Object.prototype.hasOwnProperty.call( object, name )
@@ -988,8 +1372,8 @@ function zuzuBoundMethod( receiver, name, fn ) {
988
1372
  enumerable: false,
989
1373
  configurable: true,
990
1374
  } );
991
- bound.invoke = function invoke( _self, args = [] ) {
992
- return fn.apply( receiver, args );
1375
+ bound.invoke = function invoke( self, args = [] ) {
1376
+ return fn.apply( self, args );
993
1377
  };
994
1378
  bound.to_String = function to_String() {
995
1379
  return methodName;
@@ -1502,6 +1886,22 @@ function zuzuGetMember( object, property ) {
1502
1886
  if ( object == null ) {
1503
1887
  return null;
1504
1888
  }
1889
+ if ( property !== 'length' ) {
1890
+ const arrayMethod = getArrayMethod( object, property );
1891
+ if ( arrayMethod && ARRAY_ZERO_ARG_METHODS.has( String( property ) ) ) {
1892
+ return arrayMethod();
1893
+ }
1894
+ if ( arrayMethod ) {
1895
+ return arrayMethod;
1896
+ }
1897
+ }
1898
+ const setMethod = getSetMethod( object, property );
1899
+ if ( setMethod && SET_ZERO_ARG_METHODS.has( String( property ) ) ) {
1900
+ return setMethod();
1901
+ }
1902
+ if ( setMethod ) {
1903
+ return setMethod;
1904
+ }
1505
1905
  const value = resolveWeakValue( object[property] );
1506
1906
  if (
1507
1907
  typeof value === 'function'
@@ -1541,6 +1941,18 @@ function zuzuCallMember( object, property, ...args ) {
1541
1941
  if ( property instanceof ZuzuMethod || ( property && property.__zuzu_method ) ) {
1542
1942
  return property.invoke( object, args );
1543
1943
  }
1944
+ const arrayMethod = getArrayMethod( object, property );
1945
+ if ( arrayMethod ) {
1946
+ return arrayMethod( ...args );
1947
+ }
1948
+ const setMethod = getSetMethod( object, property );
1949
+ if ( setMethod ) {
1950
+ return setMethod( ...args );
1951
+ }
1952
+ const dictMethod = getDictMethod( object, property );
1953
+ if ( dictMethod ) {
1954
+ return dictMethod( ...args );
1955
+ }
1544
1956
  const value = resolveWeakValue( object[property] );
1545
1957
  if ( typeof value === 'function' ) {
1546
1958
  return value.apply( object, args );
@@ -1553,22 +1965,24 @@ function zuzuCallMember( object, property, ...args ) {
1553
1965
  }
1554
1966
  proto = Object.getPrototypeOf( proto );
1555
1967
  }
1556
- const dictMethod = getDictMethod( object, property );
1557
- if ( dictMethod ) {
1558
- return dictMethod( ...args );
1559
- }
1560
1968
  throw new TypeError( `${String( property )} is not a function` );
1561
1969
  }
1562
1970
 
1563
- function zuzuMaybeDemolish( value ) {
1971
+ function zuzuMaybeDemolish( value, preserved = null ) {
1564
1972
  const original = value;
1565
1973
  value = resolveWeakValue( value );
1566
- let result = null;
1567
- if ( value && typeof value.__demolish__ === 'function' ) {
1568
- result = value.__demolish__();
1974
+ const shouldDemolish =
1975
+ value
1976
+ && value !== resolveWeakValue( preserved )
1977
+ && typeof value.__demolish__ === 'function';
1978
+ releaseValue( original, {
1979
+ preserveCollectionValues: value === resolveWeakValue( preserved ),
1980
+ suppressDemolish: true,
1981
+ } );
1982
+ if ( shouldDemolish && strongReferenceCount( value ) === 0 ) {
1983
+ return value.__demolish__();
1569
1984
  }
1570
- releaseValue( original );
1571
- return result;
1985
+ return null;
1572
1986
  }
1573
1987
 
1574
1988
  function zuzuCan( value, methodName ) {
@@ -1577,6 +1991,12 @@ function zuzuCan( value, methodName ) {
1577
1991
  return 0;
1578
1992
  }
1579
1993
  const key = String( methodName || '' );
1994
+ if ( getArrayMethod( value, key ) ) {
1995
+ return 1;
1996
+ }
1997
+ if ( getSetMethod( value, key ) ) {
1998
+ return 1;
1999
+ }
1580
2000
  if ( getDictMethod( value, key ) ) {
1581
2001
  return 1;
1582
2002
  }
@@ -2094,15 +2514,25 @@ class ZuzuScript {
2094
2514
  this.transpiler = normalizeTranspilerName(
2095
2515
  options.transpiler || DEFAULT_TRANSPILER
2096
2516
  );
2517
+ this.asyncImports = options.asyncImports === true;
2097
2518
  if ( typeof this.host.loadJsModule !== 'function' ) {
2098
2519
  this.host.loadJsModule = ( filename ) => require( filename );
2099
2520
  }
2100
2521
  }
2101
2522
 
2523
+ usesAsyncImports() {
2524
+ return this.asyncImports
2525
+ || (
2526
+ typeof this.host.hasAsyncModules === 'function'
2527
+ && this.host.hasAsyncModules()
2528
+ );
2529
+ }
2530
+
2102
2531
  transpile( source, options = {} ) {
2103
2532
  return transpile( source, {
2104
2533
  ...options,
2105
2534
  transpiler: options.transpiler || this.transpiler,
2535
+ asyncImports: options.asyncImports ?? this.usesAsyncImports(),
2106
2536
  } );
2107
2537
  }
2108
2538
 
@@ -2474,6 +2904,7 @@ class ZuzuScript {
2474
2904
  defineRuntimeMethod( Set.prototype, 'empty', function _emptySet() { return this.size === 0 ? 1 : 0; } );
2475
2905
  defineRuntimeMethod( Set.prototype, 'is_empty', function _isEmptySet() { return this.empty(); } );
2476
2906
  defineRuntimeMethod( Set.prototype, 'push', function _pushSet( ...values ) { for ( const v of values ) { addSetValue( this, v ); } return this; } );
2907
+ defineRuntimeMethod( Set.prototype, 'push_weak', function _pushWeakSet( ...values ) { for ( const v of values ) { this.add( makeWeakValue( v ) ); } return this; } );
2477
2908
  defineRuntimeMethod( Set.prototype, 'contains', function _containsSet( value ) { return contains( this, value ); } );
2478
2909
  defineRuntimeMethod( Set.prototype, 'remove', function _removeSet( value ) { const resolved = resolveWeakValue( value ); if ( this.delete( resolved ) ) { releaseCollectionValue( this, resolved ); } return this; } );
2479
2910
  defineRuntimeMethod( Set.prototype, 'to_Array', function _setToArray() { return makeArray( this ); } );
@@ -2559,6 +2990,7 @@ class ZuzuScript {
2559
2990
  __file__: this.fileValueForFilename( filename ),
2560
2991
  __zuzu_system_seed: systemGlobalSeed,
2561
2992
  __zuzu_current_system: currentSystem,
2993
+ __zuzu_operator_string: zuzuOperatorString,
2562
2994
  DEBUG: this.debugLevel,
2563
2995
  __zuzu_host_capabilities: capabilityFlags,
2564
2996
  has_capability( name ) {
@@ -2922,9 +3354,9 @@ class ZuzuScript {
2922
3354
  __zuzu_length: operatorLength,
2923
3355
  __zuzu_num( value ) { return zuzuToNumber( value ); },
2924
3356
  __zuzu_truthy( value ) { return zuzuTruthy( value ); },
2925
- __zuzu_not( value ) { return zuzuTruthy( value ) ? 0 : 1; },
2926
- __zuzu_and( left, right ) { return zuzuTruthy( left ) && zuzuTruthy( right ) ? 1 : 0; },
2927
- __zuzu_or( left, right ) { return zuzuTruthy( left ) || zuzuTruthy( right ) ? 1 : 0; },
3357
+ __zuzu_not( value ) { return !zuzuTruthy( value ); },
3358
+ __zuzu_and( left, right ) { return zuzuTruthy( left ) && zuzuTruthy( right ); },
3359
+ __zuzu_or( left, right ) { return zuzuTruthy( left ) || zuzuTruthy( right ); },
2928
3360
  __zuzu_typeof( value ) { return zuzuTypeof( value ); },
2929
3361
  __zuzu_instanceof( value, klass ) { return zuzuInstanceof( value, klass ); },
2930
3362
  __zuzu_trait( name, methods, source = null, captures = {} ) {
@@ -3100,11 +3532,11 @@ class ZuzuScript {
3100
3532
  __zuzu_resolve_brace_key( object, literalProperty, getDynamicProperty ) {
3101
3533
  return zuzuResolveBraceKey( object, literalProperty, getDynamicProperty );
3102
3534
  },
3103
- __zuzu_maybe_demolish( value ) {
3104
- return zuzuMaybeDemolish( value );
3535
+ __zuzu_maybe_demolish( value, preserved = null ) {
3536
+ return zuzuMaybeDemolish( value, preserved );
3105
3537
  },
3106
- __zuzu_xor( left, right ) { return ( zuzuTruthy( left ) !== zuzuTruthy( right ) ) ? 1 : 0; },
3107
- __zuzu_nand( left, right ) { return ( zuzuTruthy( left ) && zuzuTruthy( right ) ) ? 0 : 1; },
3538
+ __zuzu_xor( left, right ) { return zuzuTruthy( left ) !== zuzuTruthy( right ); },
3539
+ __zuzu_nand( left, right ) { return !( zuzuTruthy( left ) && zuzuTruthy( right ) ); },
3108
3540
  __zuzu_num_eq( left, right ) {
3109
3541
  if ( !isNumericComparable( left ) || !isNumericComparable( right ) ) {
3110
3542
  return zuzuEqual( left, right );
@@ -3183,6 +3615,9 @@ class ZuzuScript {
3183
3615
  if ( name === 'std/eval' ) {
3184
3616
  return { eval: context.__zuzu_native_eval };
3185
3617
  }
3618
+ if ( this.usesAsyncImports() ) {
3619
+ return this.loadModuleAsync( name, filename, context );
3620
+ }
3186
3621
  return this.loadModule( name, filename, context );
3187
3622
  };
3188
3623
  return context;
@@ -3260,6 +3695,41 @@ class ZuzuScript {
3260
3695
  Object.defineProperty( proto, name, desc );
3261
3696
  }
3262
3697
  };
3698
+ if ( Array.prototype.join.__zuzu_array_join !== true ) {
3699
+ const __zuzu_native_array_join = Array.prototype.join;
3700
+ const joinDesc = Object.create( null );
3701
+ joinDesc.value = function _join( separator, fallback ) {
3702
+ const hasFallback = arguments.length > 1;
3703
+ let fallbackString;
3704
+ const sep = __zuzu_operator_string( separator );
3705
+ const out = [];
3706
+ for ( const value of this ) {
3707
+ try {
3708
+ out.push( __zuzu_operator_string( value ) );
3709
+ }
3710
+ catch ( err ) {
3711
+ if ( !hasFallback ) {
3712
+ throw err;
3713
+ }
3714
+ if ( typeof fallback === 'function' ) {
3715
+ out.push( __zuzu_operator_string( fallback( value ) ) );
3716
+ }
3717
+ else {
3718
+ if ( fallbackString === undefined ) {
3719
+ fallbackString = __zuzu_operator_string( fallback );
3720
+ }
3721
+ out.push( fallbackString );
3722
+ }
3723
+ }
3724
+ }
3725
+ return __zuzu_native_array_join.call( out, sep );
3726
+ };
3727
+ joinDesc.value.__zuzu_array_join = true;
3728
+ joinDesc.enumerable = false;
3729
+ joinDesc.configurable = true;
3730
+ joinDesc.writable = true;
3731
+ Object.defineProperty( Array.prototype, 'join', joinDesc );
3732
+ }
3263
3733
  define( Array.prototype, 'length', function _length() { return this.length; } );
3264
3734
  define( Array.prototype, 'count', function _count() { return this.length; } );
3265
3735
  define( Array.prototype, 'empty', function _empty() { return this.length === 0 ? 1 : 0; } );
@@ -3302,6 +3772,7 @@ class ZuzuScript {
3302
3772
  define( Set.prototype, 'is_empty', function _is_empty() { return this.empty(); } );
3303
3773
  define( Set.prototype, 'push', function _push( ...values ) { for ( const v of values ) { __zuzu_add_set_value( this, v ); } return this; } );
3304
3774
  define( Set.prototype, 'add_weak', function _add_weak( value ) { this.add( __zuzu_make_weak_value( value ) ); return this; } );
3775
+ define( Set.prototype, 'push_weak', function _push_weak( ...values ) { for ( const v of values ) { this.add( __zuzu_make_weak_value( v ) ); } return this; } );
3305
3776
  define( Set.prototype, 'contains', function _contains( value ) { return __zuzu_contains( this, value ); } );
3306
3777
  define( Set.prototype, 'remove', function _remove( value ) { const resolved = __zuzu_resolve_weak_value( value ); if ( this.delete( resolved ) ) { __zuzu_release_collection_value( this, resolved ); } return this; } );
3307
3778
  define( Set.prototype, 'to_Array', function _to_array() { return __zuzu_array( this ); } );
@@ -3553,6 +4024,74 @@ class ZuzuScript {
3553
4024
  return moduleObj.exports;
3554
4025
  }
3555
4026
 
4027
+ async loadModuleAsync( moduleName, fromFile, contextForPolicy = null ) {
4028
+ if ( /(^|\/)\.\.(\/|$)/.test( moduleName ) ) {
4029
+ throw new Error( "Import module path cannot contain '..' segments" );
4030
+ }
4031
+ this.enforceModulePolicy( moduleName );
4032
+ const resolved = this.resolveModulePath( moduleName, fromFile );
4033
+ const cacheable = moduleName !== 'test/more'
4034
+ && this.evalCapabilityOverrides == null;
4035
+ if ( cacheable && this.moduleCache.has( resolved ) ) {
4036
+ return this.moduleCache.get( resolved );
4037
+ }
4038
+ if ( resolved.endsWith( '.js' ) ) {
4039
+ return this.loadModule( moduleName, fromFile, contextForPolicy );
4040
+ }
4041
+ if ( this.moduleLoading.has( resolved ) ) {
4042
+ throw new Error( 'Circular module loading detected' );
4043
+ }
4044
+ this.moduleLoading.add( resolved );
4045
+ try {
4046
+ const source = typeof this.host.readFileTextAsync === 'function'
4047
+ ? await this.host.readFileTextAsync( resolved )
4048
+ : this.host.readFileText( resolved );
4049
+ let js = this.transpile( source, {
4050
+ asyncImports: true,
4051
+ deferAsyncWrapper: true,
4052
+ } );
4053
+ const exportNames = resolved.endsWith( '.zzm' )
4054
+ ? collectTopLevelDeclarations( source, stripPod )
4055
+ : [];
4056
+ let exportBridge = '';
4057
+ if ( exportNames.length > 0 ) {
4058
+ exportBridge = exportNames
4059
+ .map( (name) => {
4060
+ if ( name.startsWith( '_' ) ) {
4061
+ return `if ( typeof ${name} !== "undefined" ) { const __zuzu_desc = Object.create( null ); __zuzu_desc.get = function() { return ${name}; }; __zuzu_desc.set = function( value ) { ${name} = value; }; __zuzu_desc.enumerable = false; __zuzu_desc.configurable = true; Object.defineProperty( module.exports, ${JSON.stringify( name )}, __zuzu_desc ); }`;
4062
+ }
4063
+ return `if ( typeof ${name} !== "undefined" ) { const __zuzu_desc = Object.create( null ); __zuzu_desc.get = function() { return ${name}; }; __zuzu_desc.set = function( value ) { ${name} = value; }; __zuzu_desc.enumerable = true; __zuzu_desc.configurable = true; Object.defineProperty( module.exports, ${JSON.stringify( name )}, __zuzu_desc ); }`;
4064
+ } )
4065
+ .join( '\n' );
4066
+ }
4067
+ js = `( async () => {\n${js}\n${exportBridge}\n} )()`;
4068
+ setCompiledSource( resolved, js );
4069
+ const moduleObj = { exports: {} };
4070
+ const context = this.buildContext( {
4071
+ exports: moduleObj.exports,
4072
+ module: moduleObj,
4073
+ filename: resolved,
4074
+ } );
4075
+ context.__global__ = Object.create( null );
4076
+ this.installCollectionMethods( context );
4077
+ const moduleRunOptions = { filename: resolved };
4078
+ if ( this.executionTimeoutMs != null ) {
4079
+ moduleRunOptions.timeout = this.executionTimeoutMs;
4080
+ }
4081
+ const result = this.host.runInContext( js, context, moduleRunOptions );
4082
+ if ( result && typeof result.then === 'function' ) {
4083
+ await result;
4084
+ }
4085
+ if ( cacheable ) {
4086
+ this.moduleCache.set( resolved, moduleObj.exports );
4087
+ }
4088
+ return moduleObj.exports;
4089
+ }
4090
+ finally {
4091
+ this.moduleLoading.delete( resolved );
4092
+ }
4093
+ }
4094
+
3556
4095
  runSource( source, options = {} ) {
3557
4096
  const filename = options.filename || this.host.join( this.repoRoot, '<inline>.zzs' );
3558
4097
  let js;