porffor 0.2.0-09999e8 → 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
  };
@@ -1759,11 +1935,14 @@ const extractTypeAnnotation = decl => {
1759
1935
  elementType = extractTypeAnnotation(a.elementType).type;
1760
1936
  }
1761
1937
 
1938
+ const typeName = type;
1762
1939
  type = typeAnnoToPorfType(type);
1763
1940
 
1941
+ if (type === TYPES._bytestring && !process.argv.includes('-bytestring')) type = TYPES.string;
1942
+
1764
1943
  // if (decl.name) console.log(decl.name, { type, elementType });
1765
1944
 
1766
- return { type, elementType };
1945
+ return { type, typeName, elementType };
1767
1946
  };
1768
1947
 
1769
1948
  const generateVar = (scope, decl) => {
@@ -1777,6 +1956,8 @@ const generateVar = (scope, decl) => {
1777
1956
  for (const x of decl.declarations) {
1778
1957
  const name = mapName(x.id.name);
1779
1958
 
1959
+ if (!name) return todo('destructuring is not supported yet');
1960
+
1780
1961
  if (x.init && isFuncType(x.init.type)) {
1781
1962
  // hack for let a = function () { ... }
1782
1963
  x.init.id = { name };
@@ -1792,7 +1973,12 @@ const generateVar = (scope, decl) => {
1792
1973
  continue; // always ignore
1793
1974
  }
1794
1975
 
1795
- let idx = allocVar(scope, name, global);
1976
+ let idx = allocVar(scope, name, global, !x.id.typeAnnotation);
1977
+
1978
+ if (typedInput && x.id.typeAnnotation) {
1979
+ addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
1980
+ }
1981
+
1796
1982
  if (x.init) {
1797
1983
  out = out.concat(generate(scope, x.init, global, name));
1798
1984
 
@@ -1802,10 +1988,6 @@ const generateVar = (scope, decl) => {
1802
1988
 
1803
1989
  // hack: this follows spec properly but is mostly unneeded 😅
1804
1990
  // out.push(...setType(scope, name, x.init ? getNodeType(scope, x.init) : TYPES.undefined));
1805
-
1806
- if (typedInput && x.id.typeAnnotation) {
1807
- addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
1808
- }
1809
1991
  }
1810
1992
 
1811
1993
  return out;
@@ -1914,6 +2096,8 @@ const generateAssign = (scope, decl) => {
1914
2096
  ];
1915
2097
  }
1916
2098
 
2099
+ if (!name) return todo('destructuring is not supported yet');
2100
+
1917
2101
  const [ local, isGlobal ] = lookupName(scope, name);
1918
2102
 
1919
2103
  if (local === undefined) {
@@ -2052,6 +2236,8 @@ const generateUnary = (scope, decl) => {
2052
2236
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
2053
2237
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
2054
2238
 
2239
+ [TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2240
+
2055
2241
  // object and internal types
2056
2242
  default: makeString(scope, 'object', false, '#typeof_result'),
2057
2243
  });
@@ -2157,8 +2343,10 @@ const generateFor = (scope, decl) => {
2157
2343
  out.push([ Opcodes.loop, Blocktype.void ]);
2158
2344
  depth.push('for');
2159
2345
 
2160
- out.push(...generate(scope, decl.test));
2161
- 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 ]);
2162
2350
  depth.push('if');
2163
2351
 
2164
2352
  out.push([ Opcodes.block, Blocktype.void ]);
@@ -2166,8 +2354,7 @@ const generateFor = (scope, decl) => {
2166
2354
  out.push(...generate(scope, decl.body));
2167
2355
  out.push([ Opcodes.end ]);
2168
2356
 
2169
- out.push(...generate(scope, decl.update));
2170
- depth.pop();
2357
+ if (decl.update) out.push(...generate(scope, decl.update));
2171
2358
 
2172
2359
  out.push([ Opcodes.br, 1 ]);
2173
2360
  out.push([ Opcodes.end ], [ Opcodes.end ]);
@@ -2224,7 +2411,13 @@ const generateForOf = (scope, decl) => {
2224
2411
  // setup local for left
2225
2412
  generate(scope, decl.left);
2226
2413
 
2227
- 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
+
2228
2421
  const [ local, isGlobal ] = lookupName(scope, leftName);
2229
2422
 
2230
2423
  depth.push('block');
@@ -2233,13 +2426,14 @@ const generateForOf = (scope, decl) => {
2233
2426
  // // todo: we should only do this for strings but we don't know at compile-time :(
2234
2427
  // hack: this is naughty and will break things!
2235
2428
  let newOut = number(0, Valtype.f64), newPointer = -1;
2236
- if (pages.hasString) {
2429
+ if (pages.hasAnyString) {
2237
2430
  0, [ newOut, newPointer ] = makeArray(scope, {
2238
2431
  rawElements: new Array(1)
2239
2432
  }, isGlobal, leftName, true, 'i16');
2240
2433
  }
2241
2434
 
2242
2435
  // set type for local
2436
+ // todo: optimize away counter and use end pointer
2243
2437
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2244
2438
  [TYPES._array]: [
2245
2439
  ...setType(scope, leftName, TYPES.number),
@@ -2364,7 +2558,7 @@ const generateThrow = (scope, decl) => {
2364
2558
  // hack: throw new X("...") -> throw "..."
2365
2559
  if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
2366
2560
  constructor = decl.argument.callee.name;
2367
- message = decl.argument.arguments[0].value;
2561
+ message = decl.argument.arguments[0]?.value ?? '';
2368
2562
  }
2369
2563
 
2370
2564
  if (tags.length === 0) tags.push({
@@ -2376,6 +2570,9 @@ const generateThrow = (scope, decl) => {
2376
2570
  let exceptId = exceptions.push({ constructor, message }) - 1;
2377
2571
  let tagIdx = tags[0].idx;
2378
2572
 
2573
+ scope.exceptions ??= [];
2574
+ scope.exceptions.push(exceptId);
2575
+
2379
2576
  // todo: write a description of how this works lol
2380
2577
 
2381
2578
  return [
@@ -2420,20 +2617,26 @@ const generateAssignPat = (scope, decl) => {
2420
2617
  };
2421
2618
 
2422
2619
  let pages = new Map();
2423
- const allocPage = (reason, type) => {
2620
+ const allocPage = (scope, reason, type) => {
2424
2621
  if (pages.has(reason)) return pages.get(reason).ind;
2425
2622
 
2426
2623
  if (reason.startsWith('array:')) pages.hasArray = true;
2427
2624
  if (reason.startsWith('string:')) pages.hasString = true;
2625
+ if (reason.startsWith('bytestring:')) pages.hasByteString = true;
2626
+ if (reason.includes('string:')) pages.hasAnyString = true;
2428
2627
 
2429
2628
  const ind = pages.size;
2430
2629
  pages.set(reason, { ind, type });
2431
2630
 
2631
+ scope.pages ??= new Map();
2632
+ scope.pages.set(reason, { ind, type });
2633
+
2432
2634
  if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
2433
2635
 
2434
2636
  return ind;
2435
2637
  };
2436
2638
 
2639
+ // todo: add scope.pages
2437
2640
  const freePage = reason => {
2438
2641
  const { ind } = pages.get(reason);
2439
2642
  pages.delete(reason);
@@ -2458,25 +2661,34 @@ const StoreOps = {
2458
2661
  f64: Opcodes.f64_store,
2459
2662
 
2460
2663
  // expects i32 input!
2461
- i16: Opcodes.i32_store16
2664
+ i8: Opcodes.i32_store8,
2665
+ i16: Opcodes.i32_store16,
2462
2666
  };
2463
2667
 
2464
2668
  let data = [];
2465
2669
 
2466
- const compileBytes = (val, itemType, signed = true) => {
2670
+ const compileBytes = (val, itemType) => {
2467
2671
  // todo: this is a mess and needs confirming / ????
2468
2672
  switch (itemType) {
2469
2673
  case 'i8': return [ val % 256 ];
2470
- case 'i16': return [ val % 256, Math.floor(val / 256) ];
2471
-
2472
- case 'i32':
2473
- case 'i64':
2474
- 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
2475
2678
 
2476
2679
  case 'f64': return ieee754_binary64(val);
2477
2680
  }
2478
2681
  };
2479
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
+
2480
2692
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2481
2693
  const out = [];
2482
2694
 
@@ -2486,7 +2698,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2486
2698
 
2487
2699
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2488
2700
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2489
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
2701
+ arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2490
2702
  }
2491
2703
 
2492
2704
  const pointer = arrays.get(name);
@@ -2506,10 +2718,13 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2506
2718
  bytes.push(...compileBytes(elements[i], itemType));
2507
2719
  }
2508
2720
 
2509
- data.push({
2721
+ const ind = data.push({
2510
2722
  offset: pointer,
2511
2723
  bytes
2512
- });
2724
+ }) - 1;
2725
+
2726
+ scope.data ??= [];
2727
+ scope.data.push(ind);
2513
2728
 
2514
2729
  // local value as pointer
2515
2730
  out.push(...number(pointer));
@@ -2532,7 +2747,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2532
2747
  out.push(
2533
2748
  ...number(0, Valtype.i32),
2534
2749
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
2535
- [ 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]) ]
2536
2751
  );
2537
2752
  }
2538
2753
 
@@ -2542,15 +2757,31 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2542
2757
  return [ out, pointer ];
2543
2758
  };
2544
2759
 
2545
- 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) => {
2546
2771
  const rawElements = new Array(str.length);
2772
+ let byteStringable = process.argv.includes('-bytestring');
2547
2773
  for (let i = 0; i < str.length; i++) {
2548
- rawElements[i] = str.charCodeAt(i);
2774
+ const c = str.charCodeAt(i);
2775
+ rawElements[i] = c;
2776
+
2777
+ if (byteStringable && c > 0xFF) byteStringable = false;
2549
2778
  }
2550
2779
 
2780
+ if (byteStringable && forceBytestring === false) byteStringable = false;
2781
+
2551
2782
  return makeArray(scope, {
2552
2783
  rawElements
2553
- }, global, name, false, 'i16')[0];
2784
+ }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2554
2785
  };
2555
2786
 
2556
2787
  let arrays = new Map();
@@ -2578,10 +2809,13 @@ export const generateMember = (scope, decl, _global, _name) => {
2578
2809
  ];
2579
2810
  }
2580
2811
 
2812
+ const object = generate(scope, decl.object);
2813
+ const property = generate(scope, decl.property);
2814
+
2581
2815
  // // todo: we should only do this for strings but we don't know at compile-time :(
2582
2816
  // hack: this is naughty and will break things!
2583
- let newOut = number(0, Valtype.f64), newPointer = -1;
2584
- if (pages.hasString) {
2817
+ let newOut = number(0, valtypeBinary), newPointer = -1;
2818
+ if (pages.hasAnyString) {
2585
2819
  0, [ newOut, newPointer ] = makeArray(scope, {
2586
2820
  rawElements: new Array(1)
2587
2821
  }, _global, _name, true, 'i16');
@@ -2590,7 +2824,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2590
2824
  return typeSwitch(scope, getNodeType(scope, decl.object), {
2591
2825
  [TYPES._array]: [
2592
2826
  // get index as valtype
2593
- ...generate(scope, decl.property),
2827
+ ...property,
2594
2828
 
2595
2829
  // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2596
2830
  Opcodes.i32_to_u,
@@ -2598,7 +2832,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2598
2832
  [ Opcodes.i32_mul ],
2599
2833
 
2600
2834
  ...(aotPointer ? [] : [
2601
- ...generate(scope, decl.object),
2835
+ ...object,
2602
2836
  Opcodes.i32_to_u,
2603
2837
  [ Opcodes.i32_add ]
2604
2838
  ]),
@@ -2617,14 +2851,14 @@ export const generateMember = (scope, decl, _global, _name) => {
2617
2851
 
2618
2852
  ...number(0, Valtype.i32), // base 0 for store later
2619
2853
 
2620
- ...generate(scope, decl.property),
2621
-
2854
+ ...property,
2622
2855
  Opcodes.i32_to_u,
2856
+
2623
2857
  ...number(ValtypeSize.i16, Valtype.i32),
2624
2858
  [ Opcodes.i32_mul ],
2625
2859
 
2626
2860
  ...(aotPointer ? [] : [
2627
- ...generate(scope, decl.object),
2861
+ ...object,
2628
2862
  Opcodes.i32_to_u,
2629
2863
  [ Opcodes.i32_add ]
2630
2864
  ]),
@@ -2641,8 +2875,36 @@ export const generateMember = (scope, decl, _global, _name) => {
2641
2875
  ...number(TYPES.string, Valtype.i32),
2642
2876
  setLastType(scope)
2643
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,
2887
+
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
+ ],
2644
2906
 
2645
- default: [ [ Opcodes.unreachable ] ]
2907
+ default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
2646
2908
  });
2647
2909
  };
2648
2910
 
@@ -2659,11 +2921,14 @@ const objectHack = node => {
2659
2921
  // if object is not identifier or another member exp, give up
2660
2922
  if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
2661
2923
 
2662
- if (!objectName) objectName = objectHack(node.object).name.slice(2);
2924
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2663
2925
 
2664
2926
  // if .length, give up (hack within a hack!)
2665
2927
  if (node.property.name === 'length') return node;
2666
2928
 
2929
+ // no object name, give up
2930
+ if (!objectName) return node;
2931
+
2667
2932
  const name = '__' + objectName + '_' + node.property.name;
2668
2933
  if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
2669
2934
 
@@ -2722,10 +2987,8 @@ const generateFunc = (scope, decl) => {
2722
2987
  const func = {
2723
2988
  name,
2724
2989
  params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
2725
- returns: innerScope.returns,
2726
- locals: innerScope.locals,
2727
- throws: innerScope.throws,
2728
- index: currentFuncIndex++
2990
+ index: currentFuncIndex++,
2991
+ ...innerScope
2729
2992
  };
2730
2993
  funcIndex[name] = func.index;
2731
2994
 
@@ -2763,6 +3026,16 @@ const generateCode = (scope, decl) => {
2763
3026
  };
2764
3027
 
2765
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
+
2766
3039
  Array: {
2767
3040
  generate: (scope, decl, global, name) => {
2768
3041
  // new Array(i0, i1, ...)