porffor 0.2.0-08a272e → 0.2.0-15592d6

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,5 +1,5 @@
1
1
  import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from "./wasmSpec.js";
2
- import { ieee754_binary64, signedLEB128, unsignedLEB128 } from "./encoding.js";
2
+ import { ieee754_binary64, signedLEB128, unsignedLEB128, encodeVector } from "./encoding.js";
3
3
  import { operatorOpcode } from "./expression.js";
4
4
  import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from "./builtins.js";
5
5
  import { PrototypeFuncs } from "./prototype.js";
@@ -55,7 +55,7 @@ const todo = msg => {
55
55
  };
56
56
 
57
57
  const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
58
- const generate = (scope, decl, global = false, name = undefined) => {
58
+ const generate = (scope, decl, global = false, name = undefined, valueUnused = false) => {
59
59
  switch (decl.type) {
60
60
  case 'BinaryExpression':
61
61
  return generateBinaryExp(scope, decl, global, name);
@@ -68,7 +68,12 @@ const generate = (scope, decl, global = false, name = undefined) => {
68
68
 
69
69
  case 'ArrowFunctionExpression':
70
70
  case 'FunctionDeclaration':
71
- generateFunc(scope, decl);
71
+ const func = generateFunc(scope, decl);
72
+
73
+ if (decl.type.endsWith('Expression')) {
74
+ return number(func.index);
75
+ }
76
+
72
77
  return [];
73
78
 
74
79
  case 'BlockStatement':
@@ -81,7 +86,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
81
86
  return generateExp(scope, decl);
82
87
 
83
88
  case 'CallExpression':
84
- return generateCall(scope, decl, global, name);
89
+ return generateCall(scope, decl, global, name, valueUnused);
85
90
 
86
91
  case 'NewExpression':
87
92
  return generateNew(scope, decl, global, name);
@@ -155,7 +160,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
155
160
 
156
161
  case 'TaggedTemplateExpression': {
157
162
  const funcs = {
158
- asm: str => {
163
+ __Porffor_wasm: str => {
159
164
  let out = [];
160
165
 
161
166
  for (const line of str.split('\n')) {
@@ -174,7 +179,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
174
179
  }
175
180
 
176
181
  if (asm[0] === 'memory') {
177
- allocPage('asm instrinsic');
182
+ allocPage(scope, 'asm instrinsic');
178
183
  // todo: add to store/load offset insts
179
184
  continue;
180
185
  }
@@ -191,31 +196,41 @@ const generate = (scope, decl, global = false, name = undefined) => {
191
196
  return out;
192
197
  },
193
198
 
194
- __internal_print_type: str => {
195
- const type = getType(scope, str) - TYPES.number;
199
+ __Porffor_bs: str => [
200
+ ...makeString(scope, str, undefined, undefined, true),
196
201
 
197
- return [
198
- ...number(type),
199
- [ Opcodes.call, importedFuncs.print ],
202
+ ...number(TYPES._bytestring, Valtype.i32),
203
+ setLastType(scope)
204
+ ],
205
+ __Porffor_s: str => [
206
+ ...makeString(scope, str, undefined, undefined, false),
200
207
 
201
- // newline
202
- ...number(10),
203
- [ Opcodes.call, importedFuncs.printChar ]
204
- ];
205
- }
206
- }
208
+ ...number(TYPES.string, Valtype.i32),
209
+ setLastType(scope)
210
+ ],
211
+ };
207
212
 
208
213
  const name = decl.tag.name;
209
214
  // hack for inline asm
210
215
  if (!funcs[name]) return todo('tagged template expressions not implemented');
211
216
 
212
- const str = decl.quasi.quasis[0].value.raw;
217
+ const { quasis, expressions } = decl.quasi;
218
+ let str = quasis[0].value.raw;
219
+
220
+ for (let i = 0; i < expressions.length; i++) {
221
+ const e = expressions[i];
222
+ str += lookupName(scope, e.name)[0];
223
+
224
+ str += quasis[i + 1].value.raw;
225
+ }
226
+
213
227
  return funcs[name](str);
214
228
  }
215
229
 
216
230
  default:
217
- if (decl.type.startsWith('TS')) {
218
- // ignore typescript nodes
231
+ // ignore typescript nodes
232
+ if (decl.type.startsWith('TS') ||
233
+ decl.type === 'ImportDeclaration' && decl.importKind === 'type') {
219
234
  return [];
220
235
  }
221
236
 
@@ -269,25 +284,25 @@ const generateIdent = (scope, decl) => {
269
284
  const name = mapName(rawName);
270
285
  let local = scope.locals[rawName];
271
286
 
272
- if (builtinVars[name]) {
287
+ if (Object.hasOwn(builtinVars, name)) {
273
288
  if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
274
289
  return builtinVars[name];
275
290
  }
276
291
 
277
- if (builtinFuncs[name] || internalConstrs[name]) {
292
+ if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
278
293
  // todo: return an actual something
279
294
  return number(1);
280
295
  }
281
296
 
282
- if (local === undefined) {
297
+ if (local?.idx === undefined) {
283
298
  // no local var with name
284
- if (importedFuncs.hasOwnProperty(name)) return number(importedFuncs[name]);
285
- if (funcIndex[name] !== undefined) return number(funcIndex[name]);
299
+ if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
300
+ if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name]);
286
301
 
287
- if (globals[name] !== undefined) return [ [ Opcodes.global_get, globals[name].idx ] ];
302
+ if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
288
303
  }
289
304
 
290
- if (local === undefined && rawName.startsWith('__')) {
305
+ if (local?.idx === undefined && rawName.startsWith('__')) {
291
306
  // return undefined if unknown key in already known var
292
307
  let parent = rawName.slice(2).split('_').slice(0, -1).join('_');
293
308
  if (parent.includes('_')) parent = '__' + parent;
@@ -296,7 +311,7 @@ const generateIdent = (scope, decl) => {
296
311
  if (!parentLookup[1]) return number(UNDEFINED);
297
312
  }
298
313
 
299
- if (local === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
314
+ if (local?.idx === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
300
315
 
301
316
  return [ [ Opcodes.local_get, local.idx ] ];
302
317
  };
@@ -680,6 +695,15 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
680
695
  [ Opcodes.i32_eqz ], */
681
696
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
682
697
  ],
698
+ [TYPES._bytestring]: [ // duplicate of string
699
+ [ Opcodes.local_get, tmp ],
700
+ ...(intIn ? [] : [ Opcodes.i32_to_u ]),
701
+
702
+ // get length
703
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
704
+
705
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
706
+ ],
683
707
  default: def
684
708
  }, intOut ? Valtype.i32 : valtypeBinary)
685
709
  ];
@@ -707,6 +731,17 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
707
731
  [ Opcodes.i32_eqz ],
708
732
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
709
733
  ],
734
+ [TYPES._bytestring]: [ // duplicate of string
735
+ [ Opcodes.local_get, tmp ],
736
+ ...(intIn ? [] : [ Opcodes.i32_to_u ]),
737
+
738
+ // get length
739
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
740
+
741
+ // if length == 0
742
+ [ Opcodes.i32_eqz ],
743
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
744
+ ],
710
745
  default: [
711
746
  // if value == 0
712
747
  [ Opcodes.local_get, tmp ],
@@ -760,11 +795,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
760
795
  return performLogicOp(scope, op, left, right, leftType, rightType);
761
796
  }
762
797
 
798
+ const knownLeft = knownType(scope, leftType);
799
+ const knownRight = knownType(scope, rightType);
800
+
763
801
  const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
764
802
  const strictOp = op === '===' || op === '!==';
765
803
 
766
804
  const startOut = [], endOut = [];
767
- const finalise = out => startOut.concat(out, endOut);
805
+ const finalize = out => startOut.concat(out, endOut);
768
806
 
769
807
  // if strict (in)equal check types match
770
808
  if (strictOp) {
@@ -809,31 +847,32 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
809
847
  // todo: if equality op and an operand is undefined, return false
810
848
  // todo: niche null hell with 0
811
849
 
812
- // if (leftType === TYPES.string || rightType === TYPES.string) {
813
- // if (op === '+') {
814
- // // string concat (a + b)
815
- // return finalise(concatStrings(scope, left, right, _global, _name, assign));
816
- // }
817
-
818
- // // not an equality op, NaN
819
- // if (!eqOp) return finalise(number(NaN));
820
-
821
- // // else leave bool ops
822
- // // todo: convert string to number if string and number/bool
823
- // // todo: string (>|>=|<|<=) string
824
-
825
- // // string comparison
826
- // if (op === '===' || op === '==') {
827
- // return finalise(compareStrings(scope, left, right));
828
- // }
829
-
830
- // if (op === '!==' || op === '!=') {
831
- // return finalise([
832
- // ...compareStrings(scope, left, right),
833
- // [ Opcodes.i32_eqz ]
834
- // ]);
835
- // }
836
- // }
850
+ // todo: this should be dynamic but for now only static
851
+ if (knownLeft === TYPES.string || knownRight === TYPES.string) {
852
+ if (op === '+') {
853
+ // string concat (a + b)
854
+ return concatStrings(scope, left, right, _global, _name, assign);
855
+ }
856
+
857
+ // not an equality op, NaN
858
+ if (!eqOp) return number(NaN);
859
+
860
+ // else leave bool ops
861
+ // todo: convert string to number if string and number/bool
862
+ // todo: string (>|>=|<|<=) string
863
+
864
+ // string comparison
865
+ if (op === '===' || op === '==') {
866
+ return compareStrings(scope, left, right);
867
+ }
868
+
869
+ if (op === '!==' || op === '!=') {
870
+ return [
871
+ ...compareStrings(scope, left, right),
872
+ [ Opcodes.i32_eqz ]
873
+ ];
874
+ }
875
+ }
837
876
 
838
877
  let ops = operatorOpcode[valtype][op];
839
878
 
@@ -843,7 +882,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
843
882
  includeBuiltin(scope, builtinName);
844
883
  const idx = funcIndex[builtinName];
845
884
 
846
- return finalise([
885
+ return finalize([
847
886
  ...left,
848
887
  ...right,
849
888
  [ Opcodes.call, idx ]
@@ -858,9 +897,6 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
858
897
  let tmpLeft, tmpRight;
859
898
  // if equal op, check if strings for compareStrings
860
899
  if (op === '===' || op === '==' || op === '!==' || op === '!=') (() => {
861
- const knownLeft = knownType(scope, leftType);
862
- const knownRight = knownType(scope, rightType);
863
-
864
900
  // todo: intelligent partial skip later
865
901
  // if neither known are string, stop this madness
866
902
  if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
@@ -900,7 +936,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
900
936
  [ Opcodes.i32_or ],
901
937
  [ Opcodes.if, Blocktype.void ],
902
938
  ...number(0, Valtype.i32),
903
- [ Opcodes.br, 1 ],
939
+ [ Opcodes.br, 2 ],
904
940
  [ Opcodes.end ],
905
941
 
906
942
  ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
@@ -918,7 +954,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
918
954
  // }
919
955
  })();
920
956
 
921
- return finalise([
957
+ return finalize([
922
958
  ...left,
923
959
  ...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
924
960
  ...right,
@@ -947,6 +983,18 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
947
983
  locals[nameParam(i)] = { idx: i, type: allLocals[i] };
948
984
  }
949
985
 
986
+ if (typeof wasm === 'function') {
987
+ const scope = {
988
+ name,
989
+ params,
990
+ locals,
991
+ returns,
992
+ localInd: allLocals.length,
993
+ };
994
+
995
+ wasm = wasm(scope, { TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString });
996
+ }
997
+
950
998
  let baseGlobalIdx, i = 0;
951
999
  for (const type of globalTypes) {
952
1000
  if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
@@ -1027,7 +1075,8 @@ const TYPES = {
1027
1075
 
1028
1076
  // these are not "typeof" types but tracked internally
1029
1077
  _array: 0x10,
1030
- _regexp: 0x11
1078
+ _regexp: 0x11,
1079
+ _bytestring: 0x12
1031
1080
  };
1032
1081
 
1033
1082
  const TYPE_NAMES = {
@@ -1041,13 +1090,17 @@ const TYPE_NAMES = {
1041
1090
  [TYPES.bigint]: 'BigInt',
1042
1091
 
1043
1092
  [TYPES._array]: 'Array',
1044
- [TYPES._regexp]: 'RegExp'
1093
+ [TYPES._regexp]: 'RegExp',
1094
+ [TYPES._bytestring]: 'ByteString'
1045
1095
  };
1046
1096
 
1047
1097
  const getType = (scope, _name) => {
1048
1098
  const name = mapName(_name);
1049
1099
 
1100
+ if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
1050
1101
  if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
1102
+
1103
+ if (typedInput && globals[name]?.metadata?.type != null) return number(globals[name].metadata.type, Valtype.i32);
1051
1104
  if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
1052
1105
 
1053
1106
  let type = TYPES.undefined;
@@ -1094,6 +1147,8 @@ const getNodeType = (scope, node) => {
1094
1147
  if (node.type === 'Literal') {
1095
1148
  if (node.regex) return TYPES._regexp;
1096
1149
 
1150
+ if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES._bytestring;
1151
+
1097
1152
  return TYPES[typeof node.value];
1098
1153
  }
1099
1154
 
@@ -1107,6 +1162,15 @@ const getNodeType = (scope, node) => {
1107
1162
 
1108
1163
  if (node.type === 'CallExpression' || node.type === 'NewExpression') {
1109
1164
  const name = node.callee.name;
1165
+ if (!name) {
1166
+ // iife
1167
+ if (scope.locals['#last_type']) return [ getLastType(scope) ];
1168
+
1169
+ // presume
1170
+ // todo: warn here?
1171
+ return TYPES.number;
1172
+ }
1173
+
1110
1174
  const func = funcs.find(x => x.name === name);
1111
1175
 
1112
1176
  if (func) {
@@ -1125,7 +1189,7 @@ const getNodeType = (scope, node) => {
1125
1189
  const spl = name.slice(2).split('_');
1126
1190
 
1127
1191
  const func = spl[spl.length - 1];
1128
- const protoFuncs = Object.values(prototypeFuncs).filter(x => x[func] != null);
1192
+ const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES._bytestring && prototypeFuncs[x][func] != null);
1129
1193
  if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1130
1194
  }
1131
1195
 
@@ -1177,6 +1241,14 @@ const getNodeType = (scope, node) => {
1177
1241
 
1178
1242
  if (node.type === 'BinaryExpression') {
1179
1243
  if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
1244
+ if (node.operator !== '+') return TYPES.number;
1245
+
1246
+ const knownLeft = knownType(scope, getNodeType(scope, node.left));
1247
+ const knownRight = knownType(scope, getNodeType(scope, node.right));
1248
+
1249
+ // todo: this should be dynamic but for now only static
1250
+ if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
1251
+
1180
1252
  return TYPES.number;
1181
1253
 
1182
1254
  // todo: string concat types
@@ -1201,7 +1273,7 @@ const getNodeType = (scope, node) => {
1201
1273
  if (node.operator === '!') return TYPES.boolean;
1202
1274
  if (node.operator === 'void') return TYPES.undefined;
1203
1275
  if (node.operator === 'delete') return TYPES.boolean;
1204
- if (node.operator === 'typeof') return TYPES.string;
1276
+ if (node.operator === 'typeof') return process.argv.includes('-bytestring') ? TYPES._bytestring : TYPES.string;
1205
1277
 
1206
1278
  return TYPES.number;
1207
1279
  }
@@ -1210,7 +1282,13 @@ const getNodeType = (scope, node) => {
1210
1282
  // hack: if something.length, number type
1211
1283
  if (node.property.name === 'length') return TYPES.number;
1212
1284
 
1213
- // we cannot guess
1285
+ // ts hack
1286
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
1287
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
1288
+
1289
+ if (scope.locals['#last_type']) return [ getLastType(scope) ];
1290
+
1291
+ // presume
1214
1292
  return TYPES.number;
1215
1293
  }
1216
1294
 
@@ -1230,8 +1308,8 @@ const getNodeType = (scope, node) => {
1230
1308
  const generateLiteral = (scope, decl, global, name) => {
1231
1309
  if (decl.value === null) return number(NULL);
1232
1310
 
1311
+ // hack: just return 1 for regex literals
1233
1312
  if (decl.regex) {
1234
- scope.regex[name] = decl.regex;
1235
1313
  return number(1);
1236
1314
  }
1237
1315
 
@@ -1244,16 +1322,7 @@ const generateLiteral = (scope, decl, global, name) => {
1244
1322
  return number(decl.value ? 1 : 0);
1245
1323
 
1246
1324
  case 'string':
1247
- const str = decl.value;
1248
- const rawElements = new Array(str.length);
1249
- let j = 0;
1250
- for (let i = 0; i < str.length; i++) {
1251
- rawElements[i] = str.charCodeAt(i);
1252
- }
1253
-
1254
- return makeArray(scope, {
1255
- rawElements
1256
- }, global, name, false, 'i16')[0];
1325
+ return makeString(scope, decl.value, global, name);
1257
1326
 
1258
1327
  default:
1259
1328
  return todo(`cannot generate literal of type ${typeof decl.value}`);
@@ -1274,9 +1343,9 @@ const countLeftover = wasm => {
1274
1343
 
1275
1344
  if (depth === 0)
1276
1345
  if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1277
- else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
1346
+ else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.i32_load8_u, Opcodes.i32_load8_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
1278
1347
  else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
1279
- else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
1348
+ else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
1280
1349
  else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1281
1350
  else if (inst[0] === Opcodes.return) count = 0;
1282
1351
  else if (inst[0] === Opcodes.call) {
@@ -1302,7 +1371,7 @@ const disposeLeftover = wasm => {
1302
1371
  const generateExp = (scope, decl) => {
1303
1372
  const expression = decl.expression;
1304
1373
 
1305
- const out = generate(scope, expression);
1374
+ const out = generate(scope, expression, undefined, undefined, true);
1306
1375
  disposeLeftover(out);
1307
1376
 
1308
1377
  return out;
@@ -1360,7 +1429,7 @@ const RTArrayUtil = {
1360
1429
  ]
1361
1430
  };
1362
1431
 
1363
- const generateCall = (scope, decl, _global, _name) => {
1432
+ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1364
1433
  /* const callee = decl.callee;
1365
1434
  const args = decl.arguments;
1366
1435
 
@@ -1426,8 +1495,8 @@ const generateCall = (scope, decl, _global, _name) => {
1426
1495
  // literal.func()
1427
1496
  if (!name && decl.callee.type === 'MemberExpression') {
1428
1497
  // megahack for /regex/.func()
1429
- if (decl.callee.object.regex) {
1430
- const funcName = decl.callee.property.name;
1498
+ const funcName = decl.callee.property.name;
1499
+ if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
1431
1500
  const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1432
1501
 
1433
1502
  funcIndex[func.name] = func.index;
@@ -1469,8 +1538,7 @@ const generateCall = (scope, decl, _global, _name) => {
1469
1538
 
1470
1539
  if (protoName) {
1471
1540
  const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1472
- const f = prototypeFuncs[x][protoName];
1473
- if (f) acc[x] = f;
1541
+ if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
1474
1542
  return acc;
1475
1543
  }, {});
1476
1544
 
@@ -1479,10 +1547,18 @@ const generateCall = (scope, decl, _global, _name) => {
1479
1547
  // use local for cached i32 length as commonly used
1480
1548
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1481
1549
  const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
1482
- const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
1483
1550
 
1484
1551
  // TODO: long-term, prototypes should be their individual separate funcs
1485
1552
 
1553
+ const rawPointer = [
1554
+ ...generate(scope, target),
1555
+ Opcodes.i32_to_u
1556
+ ];
1557
+
1558
+ const usePointerCache = !Object.values(protoCands).every(x => x.noPointerCache === true);
1559
+ const getPointer = usePointerCache ? [ [ Opcodes.local_get, pointerLocal ] ] : rawPointer;
1560
+
1561
+ let allOptUnused = true;
1486
1562
  let lengthI32CacheUsed = false;
1487
1563
  const protoBC = {};
1488
1564
  for (const x in protoCands) {
@@ -1502,6 +1578,7 @@ const generateCall = (scope, decl, _global, _name) => {
1502
1578
  const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
1503
1579
  const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
1504
1580
 
1581
+ let optUnused = false;
1505
1582
  const protoOut = protoFunc(getPointer, {
1506
1583
  getCachedI32: () => {
1507
1584
  lengthI32CacheUsed = true;
@@ -1516,10 +1593,15 @@ const generateCall = (scope, decl, _global, _name) => {
1516
1593
  return makeArray(scope, {
1517
1594
  rawElements: new Array(length)
1518
1595
  }, _global, _name, true, itemType);
1596
+ }, () => {
1597
+ optUnused = true;
1598
+ return unusedValue;
1519
1599
  });
1520
1600
 
1601
+ if (!optUnused) allOptUnused = false;
1602
+
1521
1603
  protoBC[x] = [
1522
- [ Opcodes.block, valtypeBinary ],
1604
+ [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1523
1605
  ...protoOut,
1524
1606
 
1525
1607
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
@@ -1528,11 +1610,13 @@ const generateCall = (scope, decl, _global, _name) => {
1528
1610
  ];
1529
1611
  }
1530
1612
 
1531
- return [
1532
- ...generate(scope, target),
1613
+ // todo: if some cands use optUnused and some don't, we will probably crash
1533
1614
 
1534
- Opcodes.i32_to_u,
1535
- [ Opcodes.local_set, pointerLocal ],
1615
+ return [
1616
+ ...(usePointerCache ? [
1617
+ ...rawPointer,
1618
+ [ Opcodes.local_set, pointerLocal ],
1619
+ ] : []),
1536
1620
 
1537
1621
  ...(!lengthI32CacheUsed ? [] : [
1538
1622
  ...RTArrayUtil.getLengthI32(getPointer),
@@ -1544,7 +1628,7 @@ const generateCall = (scope, decl, _global, _name) => {
1544
1628
 
1545
1629
  // TODO: error better
1546
1630
  default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1547
- }, valtypeBinary),
1631
+ }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1548
1632
  ];
1549
1633
  }
1550
1634
  }
@@ -1591,7 +1675,9 @@ const generateCall = (scope, decl, _global, _name) => {
1591
1675
  const func = funcs.find(x => x.index === idx);
1592
1676
 
1593
1677
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1594
- const paramCount = func && (userFunc ? func.params.length / 2 : func.params.length);
1678
+ const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1679
+ const typedReturns = userFunc || builtinFuncs[name]?.typedReturns;
1680
+ const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1595
1681
 
1596
1682
  let args = decl.arguments;
1597
1683
  if (func && args.length < paramCount) {
@@ -1609,12 +1695,12 @@ const generateCall = (scope, decl, _global, _name) => {
1609
1695
  let out = [];
1610
1696
  for (const arg of args) {
1611
1697
  out = out.concat(generate(scope, arg));
1612
- if (userFunc) out = out.concat(getNodeType(scope, arg));
1698
+ if (typedParams) out = out.concat(getNodeType(scope, arg));
1613
1699
  }
1614
1700
 
1615
1701
  out.push([ Opcodes.call, idx ]);
1616
1702
 
1617
- if (!userFunc) {
1703
+ if (!typedReturns) {
1618
1704
  // let type;
1619
1705
  // if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
1620
1706
  // if (internalConstrs[name]) type = internalConstrs[name].type;
@@ -1665,14 +1751,102 @@ const knownType = (scope, type) => {
1665
1751
  return null;
1666
1752
  };
1667
1753
 
1754
+ const brTable = (input, bc, returns) => {
1755
+ const out = [];
1756
+ const keys = Object.keys(bc);
1757
+ const count = keys.length;
1758
+
1759
+ if (count === 1) {
1760
+ // return [
1761
+ // ...input,
1762
+ // ...bc[keys[0]]
1763
+ // ];
1764
+ return bc[keys[0]];
1765
+ }
1766
+
1767
+ if (count === 2) {
1768
+ // just use if else
1769
+ const other = keys.find(x => x !== 'default');
1770
+ return [
1771
+ ...input,
1772
+ ...number(other, Valtype.i32),
1773
+ [ Opcodes.i32_eq ],
1774
+ [ Opcodes.if, returns ],
1775
+ ...bc[other],
1776
+ [ Opcodes.else ],
1777
+ ...bc.default,
1778
+ [ Opcodes.end ]
1779
+ ];
1780
+ }
1781
+
1782
+ for (let i = 0; i < count; i++) {
1783
+ if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
1784
+ else out.push([ Opcodes.block, Blocktype.void ]);
1785
+ }
1786
+
1787
+ const nums = keys.filter(x => +x);
1788
+ const offset = Math.min(...nums);
1789
+ const max = Math.max(...nums);
1790
+
1791
+ const table = [];
1792
+ let br = 1;
1793
+
1794
+ for (let i = offset; i <= max; i++) {
1795
+ // if branch for this num, go to that block
1796
+ if (bc[i]) {
1797
+ table.push(br);
1798
+ br++;
1799
+ continue;
1800
+ }
1801
+
1802
+ // else default
1803
+ table.push(0);
1804
+ }
1805
+
1806
+ out.push(
1807
+ [ Opcodes.block, Blocktype.void ],
1808
+ ...input,
1809
+ ...(offset > 0 ? [
1810
+ ...number(offset, Valtype.i32),
1811
+ [ Opcodes.i32_sub ]
1812
+ ] : []),
1813
+ [ Opcodes.br_table, ...encodeVector(table), 0 ]
1814
+ );
1815
+
1816
+ // if you can guess why we sort the wrong way and then reverse
1817
+ // (instead of just sorting the correct way)
1818
+ // dm me and if you are correct and the first person
1819
+ // I will somehow shout you out or something
1820
+ const orderedBc = keys.sort((a, b) => b - a).reverse();
1821
+
1822
+ br = count - 1;
1823
+ for (const x of orderedBc) {
1824
+ out.push(
1825
+ [ Opcodes.end ],
1826
+ ...bc[x],
1827
+ ...(br === 0 ? [] : [ [ Opcodes.br, br ] ])
1828
+ );
1829
+ br--;
1830
+ }
1831
+
1832
+ return [
1833
+ ...out,
1834
+ [ Opcodes.end, 'br table end' ]
1835
+ ];
1836
+ };
1837
+
1668
1838
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1839
+ if (!process.argv.includes('-bytestring')) delete bc[TYPES._bytestring];
1840
+
1669
1841
  const known = knownType(scope, type);
1670
1842
  if (known != null) {
1671
1843
  return bc[known] ?? bc.default;
1672
1844
  }
1673
1845
 
1674
- const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
1846
+ if (process.argv.includes('-typeswitch-use-brtable'))
1847
+ return brTable(type, bc, returns);
1675
1848
 
1849
+ const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
1676
1850
  const out = [
1677
1851
  ...type,
1678
1852
  [ Opcodes.local_set, tmp ],
@@ -1704,7 +1878,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1704
1878
  return out;
1705
1879
  };
1706
1880
 
1707
- const allocVar = (scope, name, global = false) => {
1881
+ const allocVar = (scope, name, global = false, type = true) => {
1708
1882
  const target = global ? globals : scope.locals;
1709
1883
 
1710
1884
  // already declared
@@ -1718,8 +1892,10 @@ const allocVar = (scope, name, global = false) => {
1718
1892
  let idx = global ? globalInd++ : scope.localInd++;
1719
1893
  target[name] = { idx, type: valtypeBinary };
1720
1894
 
1721
- let typeIdx = global ? globalInd++ : scope.localInd++;
1722
- target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
1895
+ if (type) {
1896
+ let typeIdx = global ? globalInd++ : scope.localInd++;
1897
+ target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
1898
+ }
1723
1899
 
1724
1900
  return idx;
1725
1901
  };
@@ -1762,6 +1938,8 @@ const extractTypeAnnotation = decl => {
1762
1938
  const typeName = type;
1763
1939
  type = typeAnnoToPorfType(type);
1764
1940
 
1941
+ if (type === TYPES._bytestring && !process.argv.includes('-bytestring')) type = TYPES.string;
1942
+
1765
1943
  // if (decl.name) console.log(decl.name, { type, elementType });
1766
1944
 
1767
1945
  return { type, typeName, elementType };
@@ -1778,6 +1956,8 @@ const generateVar = (scope, decl) => {
1778
1956
  for (const x of decl.declarations) {
1779
1957
  const name = mapName(x.id.name);
1780
1958
 
1959
+ if (!name) return todo('destructuring is not supported yet');
1960
+
1781
1961
  if (x.init && isFuncType(x.init.type)) {
1782
1962
  // hack for let a = function () { ... }
1783
1963
  x.init.id = { name };
@@ -1793,7 +1973,7 @@ const generateVar = (scope, decl) => {
1793
1973
  continue; // always ignore
1794
1974
  }
1795
1975
 
1796
- let idx = allocVar(scope, name, global);
1976
+ let idx = allocVar(scope, name, global, !x.id.typeAnnotation);
1797
1977
 
1798
1978
  if (typedInput && x.id.typeAnnotation) {
1799
1979
  addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
@@ -1916,6 +2096,8 @@ const generateAssign = (scope, decl) => {
1916
2096
  ];
1917
2097
  }
1918
2098
 
2099
+ if (!name) return todo('destructuring is not supported yet');
2100
+
1919
2101
  const [ local, isGlobal ] = lookupName(scope, name);
1920
2102
 
1921
2103
  if (local === undefined) {
@@ -2054,6 +2236,8 @@ const generateUnary = (scope, decl) => {
2054
2236
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
2055
2237
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
2056
2238
 
2239
+ [TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2240
+
2057
2241
  // object and internal types
2058
2242
  default: makeString(scope, 'object', false, '#typeof_result'),
2059
2243
  });
@@ -2159,8 +2343,10 @@ const generateFor = (scope, decl) => {
2159
2343
  out.push([ Opcodes.loop, Blocktype.void ]);
2160
2344
  depth.push('for');
2161
2345
 
2162
- out.push(...generate(scope, decl.test));
2163
- out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
2346
+ if (decl.test) out.push(...generate(scope, decl.test), Opcodes.i32_to);
2347
+ else out.push(...number(1, Valtype.i32));
2348
+
2349
+ out.push([ Opcodes.if, Blocktype.void ]);
2164
2350
  depth.push('if');
2165
2351
 
2166
2352
  out.push([ Opcodes.block, Blocktype.void ]);
@@ -2168,8 +2354,7 @@ const generateFor = (scope, decl) => {
2168
2354
  out.push(...generate(scope, decl.body));
2169
2355
  out.push([ Opcodes.end ]);
2170
2356
 
2171
- out.push(...generate(scope, decl.update));
2172
- depth.pop();
2357
+ if (decl.update) out.push(...generate(scope, decl.update));
2173
2358
 
2174
2359
  out.push([ Opcodes.br, 1 ]);
2175
2360
  out.push([ Opcodes.end ], [ Opcodes.end ]);
@@ -2226,7 +2411,13 @@ const generateForOf = (scope, decl) => {
2226
2411
  // setup local for left
2227
2412
  generate(scope, decl.left);
2228
2413
 
2229
- const leftName = decl.left.declarations[0].id.name;
2414
+ let leftName = decl.left.declarations?.[0]?.id?.name;
2415
+ if (!leftName && decl.left.name) {
2416
+ leftName = decl.left.name;
2417
+
2418
+ generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
2419
+ }
2420
+
2230
2421
  const [ local, isGlobal ] = lookupName(scope, leftName);
2231
2422
 
2232
2423
  depth.push('block');
@@ -2235,13 +2426,14 @@ const generateForOf = (scope, decl) => {
2235
2426
  // // todo: we should only do this for strings but we don't know at compile-time :(
2236
2427
  // hack: this is naughty and will break things!
2237
2428
  let newOut = number(0, Valtype.f64), newPointer = -1;
2238
- if (pages.hasString) {
2429
+ if (pages.hasAnyString) {
2239
2430
  0, [ newOut, newPointer ] = makeArray(scope, {
2240
2431
  rawElements: new Array(1)
2241
2432
  }, isGlobal, leftName, true, 'i16');
2242
2433
  }
2243
2434
 
2244
2435
  // set type for local
2436
+ // todo: optimize away counter and use end pointer
2245
2437
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2246
2438
  [TYPES._array]: [
2247
2439
  ...setType(scope, leftName, TYPES.number),
@@ -2366,7 +2558,7 @@ const generateThrow = (scope, decl) => {
2366
2558
  // hack: throw new X("...") -> throw "..."
2367
2559
  if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
2368
2560
  constructor = decl.argument.callee.name;
2369
- message = decl.argument.arguments[0].value;
2561
+ message = decl.argument.arguments[0]?.value ?? '';
2370
2562
  }
2371
2563
 
2372
2564
  if (tags.length === 0) tags.push({
@@ -2378,6 +2570,9 @@ const generateThrow = (scope, decl) => {
2378
2570
  let exceptId = exceptions.push({ constructor, message }) - 1;
2379
2571
  let tagIdx = tags[0].idx;
2380
2572
 
2573
+ scope.exceptions ??= [];
2574
+ scope.exceptions.push(exceptId);
2575
+
2381
2576
  // todo: write a description of how this works lol
2382
2577
 
2383
2578
  return [
@@ -2422,20 +2617,26 @@ const generateAssignPat = (scope, decl) => {
2422
2617
  };
2423
2618
 
2424
2619
  let pages = new Map();
2425
- const allocPage = (reason, type) => {
2620
+ const allocPage = (scope, reason, type) => {
2426
2621
  if (pages.has(reason)) return pages.get(reason).ind;
2427
2622
 
2428
2623
  if (reason.startsWith('array:')) pages.hasArray = true;
2429
2624
  if (reason.startsWith('string:')) pages.hasString = true;
2625
+ if (reason.startsWith('bytestring:')) pages.hasByteString = true;
2626
+ if (reason.includes('string:')) pages.hasAnyString = true;
2430
2627
 
2431
2628
  const ind = pages.size;
2432
2629
  pages.set(reason, { ind, type });
2433
2630
 
2631
+ scope.pages ??= new Map();
2632
+ scope.pages.set(reason, { ind, type });
2633
+
2434
2634
  if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
2435
2635
 
2436
2636
  return ind;
2437
2637
  };
2438
2638
 
2639
+ // todo: add scope.pages
2439
2640
  const freePage = reason => {
2440
2641
  const { ind } = pages.get(reason);
2441
2642
  pages.delete(reason);
@@ -2460,25 +2661,34 @@ const StoreOps = {
2460
2661
  f64: Opcodes.f64_store,
2461
2662
 
2462
2663
  // expects i32 input!
2463
- i16: Opcodes.i32_store16
2664
+ i8: Opcodes.i32_store8,
2665
+ i16: Opcodes.i32_store16,
2464
2666
  };
2465
2667
 
2466
2668
  let data = [];
2467
2669
 
2468
- const compileBytes = (val, itemType, signed = true) => {
2670
+ const compileBytes = (val, itemType) => {
2469
2671
  // todo: this is a mess and needs confirming / ????
2470
2672
  switch (itemType) {
2471
2673
  case 'i8': return [ val % 256 ];
2472
- case 'i16': return [ val % 256, Math.floor(val / 256) ];
2473
-
2474
- case 'i32':
2475
- case 'i64':
2476
- return enforceFourBytes(signedLEB128(val));
2674
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
2675
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
2676
+ case 'i32': return [...new Uint8Array(new Int32Array([ val ]).buffer)];
2677
+ // todo: i64
2477
2678
 
2478
2679
  case 'f64': return ieee754_binary64(val);
2479
2680
  }
2480
2681
  };
2481
2682
 
2683
+ const getAllocType = itemType => {
2684
+ switch (itemType) {
2685
+ case 'i8': return 'bytestring';
2686
+ case 'i16': return 'string';
2687
+
2688
+ default: return 'array';
2689
+ }
2690
+ };
2691
+
2482
2692
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2483
2693
  const out = [];
2484
2694
 
@@ -2488,7 +2698,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2488
2698
 
2489
2699
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2490
2700
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2491
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
2701
+ arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2492
2702
  }
2493
2703
 
2494
2704
  const pointer = arrays.get(name);
@@ -2508,10 +2718,13 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2508
2718
  bytes.push(...compileBytes(elements[i], itemType));
2509
2719
  }
2510
2720
 
2511
- data.push({
2721
+ const ind = data.push({
2512
2722
  offset: pointer,
2513
2723
  bytes
2514
- });
2724
+ }) - 1;
2725
+
2726
+ scope.data ??= [];
2727
+ scope.data.push(ind);
2515
2728
 
2516
2729
  // local value as pointer
2517
2730
  out.push(...number(pointer));
@@ -2534,7 +2747,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2534
2747
  out.push(
2535
2748
  ...number(0, Valtype.i32),
2536
2749
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
2537
- [ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2750
+ [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2538
2751
  );
2539
2752
  }
2540
2753
 
@@ -2544,15 +2757,31 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2544
2757
  return [ out, pointer ];
2545
2758
  };
2546
2759
 
2547
- const makeString = (scope, str, global = false, name = '$undeclared') => {
2760
+ const byteStringable = str => {
2761
+ if (!process.argv.includes('-bytestring')) return false;
2762
+
2763
+ for (let i = 0; i < str.length; i++) {
2764
+ if (str.charCodeAt(i) > 0xFF) return false;
2765
+ }
2766
+
2767
+ return true;
2768
+ };
2769
+
2770
+ const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
2548
2771
  const rawElements = new Array(str.length);
2772
+ let byteStringable = process.argv.includes('-bytestring');
2549
2773
  for (let i = 0; i < str.length; i++) {
2550
- rawElements[i] = str.charCodeAt(i);
2774
+ const c = str.charCodeAt(i);
2775
+ rawElements[i] = c;
2776
+
2777
+ if (byteStringable && c > 0xFF) byteStringable = false;
2551
2778
  }
2552
2779
 
2780
+ if (byteStringable && forceBytestring === false) byteStringable = false;
2781
+
2553
2782
  return makeArray(scope, {
2554
2783
  rawElements
2555
- }, global, name, false, 'i16')[0];
2784
+ }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2556
2785
  };
2557
2786
 
2558
2787
  let arrays = new Map();
@@ -2580,10 +2809,13 @@ export const generateMember = (scope, decl, _global, _name) => {
2580
2809
  ];
2581
2810
  }
2582
2811
 
2812
+ const object = generate(scope, decl.object);
2813
+ const property = generate(scope, decl.property);
2814
+
2583
2815
  // // todo: we should only do this for strings but we don't know at compile-time :(
2584
2816
  // hack: this is naughty and will break things!
2585
2817
  let newOut = number(0, valtypeBinary), newPointer = -1;
2586
- if (pages.hasString) {
2818
+ if (pages.hasAnyString) {
2587
2819
  0, [ newOut, newPointer ] = makeArray(scope, {
2588
2820
  rawElements: new Array(1)
2589
2821
  }, _global, _name, true, 'i16');
@@ -2592,7 +2824,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2592
2824
  return typeSwitch(scope, getNodeType(scope, decl.object), {
2593
2825
  [TYPES._array]: [
2594
2826
  // get index as valtype
2595
- ...generate(scope, decl.property),
2827
+ ...property,
2596
2828
 
2597
2829
  // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2598
2830
  Opcodes.i32_to_u,
@@ -2600,7 +2832,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2600
2832
  [ Opcodes.i32_mul ],
2601
2833
 
2602
2834
  ...(aotPointer ? [] : [
2603
- ...generate(scope, decl.object),
2835
+ ...object,
2604
2836
  Opcodes.i32_to_u,
2605
2837
  [ Opcodes.i32_add ]
2606
2838
  ]),
@@ -2619,14 +2851,14 @@ export const generateMember = (scope, decl, _global, _name) => {
2619
2851
 
2620
2852
  ...number(0, Valtype.i32), // base 0 for store later
2621
2853
 
2622
- ...generate(scope, decl.property),
2623
-
2854
+ ...property,
2624
2855
  Opcodes.i32_to_u,
2856
+
2625
2857
  ...number(ValtypeSize.i16, Valtype.i32),
2626
2858
  [ Opcodes.i32_mul ],
2627
2859
 
2628
2860
  ...(aotPointer ? [] : [
2629
- ...generate(scope, decl.object),
2861
+ ...object,
2630
2862
  Opcodes.i32_to_u,
2631
2863
  [ Opcodes.i32_add ]
2632
2864
  ]),
@@ -2643,8 +2875,36 @@ export const generateMember = (scope, decl, _global, _name) => {
2643
2875
  ...number(TYPES.string, Valtype.i32),
2644
2876
  setLastType(scope)
2645
2877
  ],
2878
+ [TYPES._bytestring]: [
2879
+ // setup new/out array
2880
+ ...newOut,
2881
+ [ Opcodes.drop ],
2882
+
2883
+ ...number(0, Valtype.i32), // base 0 for store later
2884
+
2885
+ ...property,
2886
+ Opcodes.i32_to_u,
2646
2887
 
2647
- default: [ [ Opcodes.unreachable ] ]
2888
+ ...(aotPointer ? [] : [
2889
+ ...object,
2890
+ Opcodes.i32_to_u,
2891
+ [ Opcodes.i32_add ]
2892
+ ]),
2893
+
2894
+ // load current string ind {arg}
2895
+ [ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2896
+
2897
+ // store to new string ind 0
2898
+ [ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2899
+
2900
+ // return new string (page)
2901
+ ...number(newPointer),
2902
+
2903
+ ...number(TYPES._bytestring, Valtype.i32),
2904
+ setLastType(scope)
2905
+ ],
2906
+
2907
+ default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
2648
2908
  });
2649
2909
  };
2650
2910
 
@@ -2661,11 +2921,14 @@ const objectHack = node => {
2661
2921
  // if object is not identifier or another member exp, give up
2662
2922
  if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
2663
2923
 
2664
- if (!objectName) objectName = objectHack(node.object).name.slice(2);
2924
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2665
2925
 
2666
2926
  // if .length, give up (hack within a hack!)
2667
2927
  if (node.property.name === 'length') return node;
2668
2928
 
2929
+ // no object name, give up
2930
+ if (!objectName) return node;
2931
+
2669
2932
  const name = '__' + objectName + '_' + node.property.name;
2670
2933
  if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
2671
2934
 
@@ -2724,10 +2987,8 @@ const generateFunc = (scope, decl) => {
2724
2987
  const func = {
2725
2988
  name,
2726
2989
  params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
2727
- returns: innerScope.returns,
2728
- locals: innerScope.locals,
2729
- throws: innerScope.throws,
2730
- index: currentFuncIndex++
2990
+ index: currentFuncIndex++,
2991
+ ...innerScope
2731
2992
  };
2732
2993
  funcIndex[name] = func.index;
2733
2994
 
@@ -2765,6 +3026,16 @@ const generateCode = (scope, decl) => {
2765
3026
  };
2766
3027
 
2767
3028
  const internalConstrs = {
3029
+ Boolean: {
3030
+ generate: (scope, decl) => {
3031
+ if (decl.arguments.length === 0) return number(0);
3032
+
3033
+ // should generate/run all args
3034
+ return truthy(scope, generate(scope, decl.arguments[0]), getNodeType(scope, decl.arguments[0]), false, false);
3035
+ },
3036
+ type: TYPES.boolean
3037
+ },
3038
+
2768
3039
  Array: {
2769
3040
  generate: (scope, decl, global, name) => {
2770
3041
  // new Array(i0, i1, ...)