porffor 0.2.0-aea77ff → 0.2.0-c1d7382

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.
@@ -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);
@@ -86,7 +86,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
86
86
  return generateExp(scope, decl);
87
87
 
88
88
  case 'CallExpression':
89
- return generateCall(scope, decl, global, name);
89
+ return generateCall(scope, decl, global, name, valueUnused);
90
90
 
91
91
  case 'NewExpression':
92
92
  return generateNew(scope, decl, global, name);
@@ -160,7 +160,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
160
160
 
161
161
  case 'TaggedTemplateExpression': {
162
162
  const funcs = {
163
- asm: str => {
163
+ __Porffor_asm: str => {
164
164
  let out = [];
165
165
 
166
166
  for (const line of str.split('\n')) {
@@ -196,19 +196,19 @@ const generate = (scope, decl, global = false, name = undefined) => {
196
196
  return out;
197
197
  },
198
198
 
199
- __internal_print_type: str => {
200
- const type = getType(scope, str) - TYPES.number;
199
+ __Porffor_bs: str => [
200
+ ...makeString(scope, str, undefined, undefined, true),
201
201
 
202
- return [
203
- ...number(type),
204
- [ 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),
205
207
 
206
- // newline
207
- ...number(10),
208
- [ Opcodes.call, importedFuncs.printChar ]
209
- ];
210
- }
211
- }
208
+ ...number(TYPES.string, Valtype.i32),
209
+ setLastType(scope)
210
+ ],
211
+ };
212
212
 
213
213
  const name = decl.tag.name;
214
214
  // hack for inline asm
@@ -274,25 +274,25 @@ const generateIdent = (scope, decl) => {
274
274
  const name = mapName(rawName);
275
275
  let local = scope.locals[rawName];
276
276
 
277
- if (builtinVars[name]) {
277
+ if (Object.hasOwn(builtinVars, name)) {
278
278
  if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
279
279
  return builtinVars[name];
280
280
  }
281
281
 
282
- if (builtinFuncs[name] || internalConstrs[name]) {
282
+ if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
283
283
  // todo: return an actual something
284
284
  return number(1);
285
285
  }
286
286
 
287
- if (local === undefined) {
287
+ if (local?.idx === undefined) {
288
288
  // no local var with name
289
- if (importedFuncs.hasOwnProperty(name)) return number(importedFuncs[name]);
290
- if (funcIndex[name] !== undefined) return number(funcIndex[name]);
289
+ if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
290
+ if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name]);
291
291
 
292
- if (globals[name] !== undefined) return [ [ Opcodes.global_get, globals[name].idx ] ];
292
+ if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
293
293
  }
294
294
 
295
- if (local === undefined && rawName.startsWith('__')) {
295
+ if (local?.idx === undefined && rawName.startsWith('__')) {
296
296
  // return undefined if unknown key in already known var
297
297
  let parent = rawName.slice(2).split('_').slice(0, -1).join('_');
298
298
  if (parent.includes('_')) parent = '__' + parent;
@@ -301,7 +301,7 @@ const generateIdent = (scope, decl) => {
301
301
  if (!parentLookup[1]) return number(UNDEFINED);
302
302
  }
303
303
 
304
- if (local === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
304
+ if (local?.idx === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
305
305
 
306
306
  return [ [ Opcodes.local_get, local.idx ] ];
307
307
  };
@@ -685,6 +685,15 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
685
685
  [ Opcodes.i32_eqz ], */
686
686
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
687
687
  ],
688
+ [TYPES._bytestring]: [ // duplicate of string
689
+ [ Opcodes.local_get, tmp ],
690
+ ...(intIn ? [] : [ Opcodes.i32_to_u ]),
691
+
692
+ // get length
693
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
694
+
695
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
696
+ ],
688
697
  default: def
689
698
  }, intOut ? Valtype.i32 : valtypeBinary)
690
699
  ];
@@ -712,6 +721,17 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
712
721
  [ Opcodes.i32_eqz ],
713
722
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
714
723
  ],
724
+ [TYPES._bytestring]: [ // duplicate of string
725
+ [ Opcodes.local_get, tmp ],
726
+ ...(intIn ? [] : [ Opcodes.i32_to_u ]),
727
+
728
+ // get length
729
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
730
+
731
+ // if length == 0
732
+ [ Opcodes.i32_eqz ],
733
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
734
+ ],
715
735
  default: [
716
736
  // if value == 0
717
737
  [ Opcodes.local_get, tmp ],
@@ -905,7 +925,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
905
925
  [ Opcodes.i32_or ],
906
926
  [ Opcodes.if, Blocktype.void ],
907
927
  ...number(0, Valtype.i32),
908
- [ Opcodes.br, 1 ],
928
+ [ Opcodes.br, 2 ],
909
929
  [ Opcodes.end ],
910
930
 
911
931
  ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
@@ -961,7 +981,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
961
981
  localInd: allLocals.length,
962
982
  };
963
983
 
964
- wasm = wasm(scope, { TYPES, typeSwitch, makeArray });
984
+ wasm = wasm(scope, { TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString });
965
985
  }
966
986
 
967
987
  let baseGlobalIdx, i = 0;
@@ -1044,7 +1064,8 @@ const TYPES = {
1044
1064
 
1045
1065
  // these are not "typeof" types but tracked internally
1046
1066
  _array: 0x10,
1047
- _regexp: 0x11
1067
+ _regexp: 0x11,
1068
+ _bytestring: 0x12
1048
1069
  };
1049
1070
 
1050
1071
  const TYPE_NAMES = {
@@ -1058,7 +1079,8 @@ const TYPE_NAMES = {
1058
1079
  [TYPES.bigint]: 'BigInt',
1059
1080
 
1060
1081
  [TYPES._array]: 'Array',
1061
- [TYPES._regexp]: 'RegExp'
1082
+ [TYPES._regexp]: 'RegExp',
1083
+ [TYPES._bytestring]: 'ByteString'
1062
1084
  };
1063
1085
 
1064
1086
  const getType = (scope, _name) => {
@@ -1111,6 +1133,8 @@ const getNodeType = (scope, node) => {
1111
1133
  if (node.type === 'Literal') {
1112
1134
  if (node.regex) return TYPES._regexp;
1113
1135
 
1136
+ if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES._bytestring;
1137
+
1114
1138
  return TYPES[typeof node.value];
1115
1139
  }
1116
1140
 
@@ -1124,6 +1148,15 @@ const getNodeType = (scope, node) => {
1124
1148
 
1125
1149
  if (node.type === 'CallExpression' || node.type === 'NewExpression') {
1126
1150
  const name = node.callee.name;
1151
+ if (!name) {
1152
+ // iife
1153
+ if (scope.locals['#last_type']) return [ getLastType(scope) ];
1154
+
1155
+ // presume
1156
+ // todo: warn here?
1157
+ return TYPES.number;
1158
+ }
1159
+
1127
1160
  const func = funcs.find(x => x.name === name);
1128
1161
 
1129
1162
  if (func) {
@@ -1142,7 +1175,7 @@ const getNodeType = (scope, node) => {
1142
1175
  const spl = name.slice(2).split('_');
1143
1176
 
1144
1177
  const func = spl[spl.length - 1];
1145
- const protoFuncs = Object.values(prototypeFuncs).filter(x => x[func] != null);
1178
+ const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES._bytestring && prototypeFuncs[x][func] != null);
1146
1179
  if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1147
1180
  }
1148
1181
 
@@ -1218,7 +1251,7 @@ const getNodeType = (scope, node) => {
1218
1251
  if (node.operator === '!') return TYPES.boolean;
1219
1252
  if (node.operator === 'void') return TYPES.undefined;
1220
1253
  if (node.operator === 'delete') return TYPES.boolean;
1221
- if (node.operator === 'typeof') return TYPES.string;
1254
+ if (node.operator === 'typeof') return process.argv.includes('-bytestring') ? TYPES._bytestring : TYPES.string;
1222
1255
 
1223
1256
  return TYPES.number;
1224
1257
  }
@@ -1227,7 +1260,13 @@ const getNodeType = (scope, node) => {
1227
1260
  // hack: if something.length, number type
1228
1261
  if (node.property.name === 'length') return TYPES.number;
1229
1262
 
1230
- // we cannot guess
1263
+ // ts hack
1264
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
1265
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
1266
+
1267
+ if (scope.locals['#last_type']) return [ getLastType(scope) ];
1268
+
1269
+ // presume
1231
1270
  return TYPES.number;
1232
1271
  }
1233
1272
 
@@ -1244,28 +1283,11 @@ const getNodeType = (scope, node) => {
1244
1283
  return ret;
1245
1284
  };
1246
1285
 
1247
- const toString = (scope, wasm, type) => {
1248
- const tmp = localTmp(scope, '#tostring_tmp');
1249
- return [
1250
- ...wasm,
1251
- [ Opcodes.local_set, tmp ],
1252
-
1253
- ...typeSwitch(scope, type, {
1254
- [TYPES.string]: [
1255
- [ Opcodes.local_get, tmp ]
1256
- ],
1257
- [TYPES.undefined]: [
1258
- // [ Opcodes.]
1259
- ]
1260
- })
1261
- ]
1262
- };
1263
-
1264
1286
  const generateLiteral = (scope, decl, global, name) => {
1265
1287
  if (decl.value === null) return number(NULL);
1266
1288
 
1289
+ // hack: just return 1 for regex literals
1267
1290
  if (decl.regex) {
1268
- scope.regex[name] = decl.regex;
1269
1291
  return number(1);
1270
1292
  }
1271
1293
 
@@ -1278,16 +1300,7 @@ const generateLiteral = (scope, decl, global, name) => {
1278
1300
  return number(decl.value ? 1 : 0);
1279
1301
 
1280
1302
  case 'string':
1281
- const str = decl.value;
1282
- const rawElements = new Array(str.length);
1283
- let j = 0;
1284
- for (let i = 0; i < str.length; i++) {
1285
- rawElements[i] = str.charCodeAt(i);
1286
- }
1287
-
1288
- return makeArray(scope, {
1289
- rawElements
1290
- }, global, name, false, 'i16')[0];
1303
+ return makeString(scope, decl.value, global, name);
1291
1304
 
1292
1305
  default:
1293
1306
  return todo(`cannot generate literal of type ${typeof decl.value}`);
@@ -1308,9 +1321,9 @@ const countLeftover = wasm => {
1308
1321
 
1309
1322
  if (depth === 0)
1310
1323
  if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1311
- 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)) {}
1324
+ 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)) {}
1312
1325
  else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
1313
- else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
1326
+ else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
1314
1327
  else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1315
1328
  else if (inst[0] === Opcodes.return) count = 0;
1316
1329
  else if (inst[0] === Opcodes.call) {
@@ -1336,7 +1349,7 @@ const disposeLeftover = wasm => {
1336
1349
  const generateExp = (scope, decl) => {
1337
1350
  const expression = decl.expression;
1338
1351
 
1339
- const out = generate(scope, expression);
1352
+ const out = generate(scope, expression, undefined, undefined, true);
1340
1353
  disposeLeftover(out);
1341
1354
 
1342
1355
  return out;
@@ -1394,7 +1407,7 @@ const RTArrayUtil = {
1394
1407
  ]
1395
1408
  };
1396
1409
 
1397
- const generateCall = (scope, decl, _global, _name) => {
1410
+ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1398
1411
  /* const callee = decl.callee;
1399
1412
  const args = decl.arguments;
1400
1413
 
@@ -1460,8 +1473,8 @@ const generateCall = (scope, decl, _global, _name) => {
1460
1473
  // literal.func()
1461
1474
  if (!name && decl.callee.type === 'MemberExpression') {
1462
1475
  // megahack for /regex/.func()
1463
- if (decl.callee.object.regex) {
1464
- const funcName = decl.callee.property.name;
1476
+ const funcName = decl.callee.property.name;
1477
+ if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
1465
1478
  const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1466
1479
 
1467
1480
  funcIndex[func.name] = func.index;
@@ -1503,8 +1516,7 @@ const generateCall = (scope, decl, _global, _name) => {
1503
1516
 
1504
1517
  if (protoName) {
1505
1518
  const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1506
- const f = prototypeFuncs[x][protoName];
1507
- if (f) acc[x] = f;
1519
+ if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
1508
1520
  return acc;
1509
1521
  }, {});
1510
1522
 
@@ -1513,10 +1525,18 @@ const generateCall = (scope, decl, _global, _name) => {
1513
1525
  // use local for cached i32 length as commonly used
1514
1526
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1515
1527
  const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
1516
- const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
1517
1528
 
1518
1529
  // TODO: long-term, prototypes should be their individual separate funcs
1519
1530
 
1531
+ const rawPointer = [
1532
+ ...generate(scope, target),
1533
+ Opcodes.i32_to_u
1534
+ ];
1535
+
1536
+ const usePointerCache = !Object.values(protoCands).every(x => x.noPointerCache === true);
1537
+ const getPointer = usePointerCache ? [ [ Opcodes.local_get, pointerLocal ] ] : rawPointer;
1538
+
1539
+ let allOptUnused = true;
1520
1540
  let lengthI32CacheUsed = false;
1521
1541
  const protoBC = {};
1522
1542
  for (const x in protoCands) {
@@ -1536,6 +1556,7 @@ const generateCall = (scope, decl, _global, _name) => {
1536
1556
  const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
1537
1557
  const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
1538
1558
 
1559
+ let optUnused = false;
1539
1560
  const protoOut = protoFunc(getPointer, {
1540
1561
  getCachedI32: () => {
1541
1562
  lengthI32CacheUsed = true;
@@ -1550,10 +1571,15 @@ const generateCall = (scope, decl, _global, _name) => {
1550
1571
  return makeArray(scope, {
1551
1572
  rawElements: new Array(length)
1552
1573
  }, _global, _name, true, itemType);
1574
+ }, () => {
1575
+ optUnused = true;
1576
+ return unusedValue;
1553
1577
  });
1554
1578
 
1579
+ if (!optUnused) allOptUnused = false;
1580
+
1555
1581
  protoBC[x] = [
1556
- [ Opcodes.block, valtypeBinary ],
1582
+ [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1557
1583
  ...protoOut,
1558
1584
 
1559
1585
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
@@ -1562,11 +1588,13 @@ const generateCall = (scope, decl, _global, _name) => {
1562
1588
  ];
1563
1589
  }
1564
1590
 
1565
- return [
1566
- ...generate(scope, target),
1591
+ // todo: if some cands use optUnused and some don't, we will probably crash
1567
1592
 
1568
- Opcodes.i32_to_u,
1569
- [ Opcodes.local_set, pointerLocal ],
1593
+ return [
1594
+ ...(usePointerCache ? [
1595
+ ...rawPointer,
1596
+ [ Opcodes.local_set, pointerLocal ],
1597
+ ] : []),
1570
1598
 
1571
1599
  ...(!lengthI32CacheUsed ? [] : [
1572
1600
  ...RTArrayUtil.getLengthI32(getPointer),
@@ -1578,7 +1606,7 @@ const generateCall = (scope, decl, _global, _name) => {
1578
1606
 
1579
1607
  // TODO: error better
1580
1608
  default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1581
- }, valtypeBinary),
1609
+ }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1582
1610
  ];
1583
1611
  }
1584
1612
  }
@@ -1786,6 +1814,8 @@ const brTable = (input, bc, returns) => {
1786
1814
  };
1787
1815
 
1788
1816
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1817
+ if (!process.argv.includes('-bytestring')) delete bc[TYPES._bytestring];
1818
+
1789
1819
  const known = knownType(scope, type);
1790
1820
  if (known != null) {
1791
1821
  return bc[known] ?? bc.default;
@@ -1884,6 +1914,8 @@ const extractTypeAnnotation = decl => {
1884
1914
  const typeName = type;
1885
1915
  type = typeAnnoToPorfType(type);
1886
1916
 
1917
+ if (type === TYPES._bytestring && !process.argv.includes('-bytestring')) type = TYPES.string;
1918
+
1887
1919
  // if (decl.name) console.log(decl.name, { type, elementType });
1888
1920
 
1889
1921
  return { type, typeName, elementType };
@@ -1900,6 +1932,8 @@ const generateVar = (scope, decl) => {
1900
1932
  for (const x of decl.declarations) {
1901
1933
  const name = mapName(x.id.name);
1902
1934
 
1935
+ if (!name) return todo('destructuring is not supported yet');
1936
+
1903
1937
  if (x.init && isFuncType(x.init.type)) {
1904
1938
  // hack for let a = function () { ... }
1905
1939
  x.init.id = { name };
@@ -2038,6 +2072,8 @@ const generateAssign = (scope, decl) => {
2038
2072
  ];
2039
2073
  }
2040
2074
 
2075
+ if (!name) return todo('destructuring is not supported yet');
2076
+
2041
2077
  const [ local, isGlobal ] = lookupName(scope, name);
2042
2078
 
2043
2079
  if (local === undefined) {
@@ -2176,6 +2212,8 @@ const generateUnary = (scope, decl) => {
2176
2212
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
2177
2213
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
2178
2214
 
2215
+ [TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2216
+
2179
2217
  // object and internal types
2180
2218
  default: makeString(scope, 'object', false, '#typeof_result'),
2181
2219
  });
@@ -2281,8 +2319,10 @@ const generateFor = (scope, decl) => {
2281
2319
  out.push([ Opcodes.loop, Blocktype.void ]);
2282
2320
  depth.push('for');
2283
2321
 
2284
- out.push(...generate(scope, decl.test));
2285
- out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
2322
+ if (decl.test) out.push(...generate(scope, decl.test), Opcodes.i32_to);
2323
+ else out.push(...number(1, Valtype.i32));
2324
+
2325
+ out.push([ Opcodes.if, Blocktype.void ]);
2286
2326
  depth.push('if');
2287
2327
 
2288
2328
  out.push([ Opcodes.block, Blocktype.void ]);
@@ -2290,8 +2330,7 @@ const generateFor = (scope, decl) => {
2290
2330
  out.push(...generate(scope, decl.body));
2291
2331
  out.push([ Opcodes.end ]);
2292
2332
 
2293
- out.push(...generate(scope, decl.update));
2294
- depth.pop();
2333
+ if (decl.update) out.push(...generate(scope, decl.update));
2295
2334
 
2296
2335
  out.push([ Opcodes.br, 1 ]);
2297
2336
  out.push([ Opcodes.end ], [ Opcodes.end ]);
@@ -2348,7 +2387,13 @@ const generateForOf = (scope, decl) => {
2348
2387
  // setup local for left
2349
2388
  generate(scope, decl.left);
2350
2389
 
2351
- const leftName = decl.left.declarations[0].id.name;
2390
+ let leftName = decl.left.declarations?.[0]?.id?.name;
2391
+ if (!leftName && decl.left.name) {
2392
+ leftName = decl.left.name;
2393
+
2394
+ generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
2395
+ }
2396
+
2352
2397
  const [ local, isGlobal ] = lookupName(scope, leftName);
2353
2398
 
2354
2399
  depth.push('block');
@@ -2357,13 +2402,14 @@ const generateForOf = (scope, decl) => {
2357
2402
  // // todo: we should only do this for strings but we don't know at compile-time :(
2358
2403
  // hack: this is naughty and will break things!
2359
2404
  let newOut = number(0, Valtype.f64), newPointer = -1;
2360
- if (pages.hasString) {
2405
+ if (pages.hasAnyString) {
2361
2406
  0, [ newOut, newPointer ] = makeArray(scope, {
2362
2407
  rawElements: new Array(1)
2363
2408
  }, isGlobal, leftName, true, 'i16');
2364
2409
  }
2365
2410
 
2366
2411
  // set type for local
2412
+ // todo: optimize away counter and use end pointer
2367
2413
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2368
2414
  [TYPES._array]: [
2369
2415
  ...setType(scope, leftName, TYPES.number),
@@ -2488,7 +2534,7 @@ const generateThrow = (scope, decl) => {
2488
2534
  // hack: throw new X("...") -> throw "..."
2489
2535
  if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
2490
2536
  constructor = decl.argument.callee.name;
2491
- message = decl.argument.arguments[0].value;
2537
+ message = decl.argument.arguments[0]?.value ?? '';
2492
2538
  }
2493
2539
 
2494
2540
  if (tags.length === 0) tags.push({
@@ -2549,6 +2595,8 @@ const allocPage = (reason, type) => {
2549
2595
 
2550
2596
  if (reason.startsWith('array:')) pages.hasArray = true;
2551
2597
  if (reason.startsWith('string:')) pages.hasString = true;
2598
+ if (reason.startsWith('bytestring:')) pages.hasByteString = true;
2599
+ if (reason.includes('string:')) pages.hasAnyString = true;
2552
2600
 
2553
2601
  const ind = pages.size;
2554
2602
  pages.set(reason, { ind, type });
@@ -2582,25 +2630,34 @@ const StoreOps = {
2582
2630
  f64: Opcodes.f64_store,
2583
2631
 
2584
2632
  // expects i32 input!
2585
- i16: Opcodes.i32_store16
2633
+ i8: Opcodes.i32_store8,
2634
+ i16: Opcodes.i32_store16,
2586
2635
  };
2587
2636
 
2588
2637
  let data = [];
2589
2638
 
2590
- const compileBytes = (val, itemType, signed = true) => {
2639
+ const compileBytes = (val, itemType) => {
2591
2640
  // todo: this is a mess and needs confirming / ????
2592
2641
  switch (itemType) {
2593
2642
  case 'i8': return [ val % 256 ];
2594
- case 'i16': return [ val % 256, Math.floor(val / 256) ];
2595
-
2596
- case 'i32':
2597
- case 'i64':
2598
- return enforceFourBytes(signedLEB128(val));
2643
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
2644
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
2645
+ case 'i32': return [...new Uint8Array(new Int32Array([ val ]).buffer)];
2646
+ // todo: i64
2599
2647
 
2600
2648
  case 'f64': return ieee754_binary64(val);
2601
2649
  }
2602
2650
  };
2603
2651
 
2652
+ const getAllocType = itemType => {
2653
+ switch (itemType) {
2654
+ case 'i8': return 'bytestring';
2655
+ case 'i16': return 'string';
2656
+
2657
+ default: return 'array';
2658
+ }
2659
+ };
2660
+
2604
2661
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2605
2662
  const out = [];
2606
2663
 
@@ -2610,7 +2667,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2610
2667
 
2611
2668
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2612
2669
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2613
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
2670
+ arrays.set(name, allocPage(`${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2614
2671
  }
2615
2672
 
2616
2673
  const pointer = arrays.get(name);
@@ -2656,7 +2713,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2656
2713
  out.push(
2657
2714
  ...number(0, Valtype.i32),
2658
2715
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
2659
- [ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2716
+ [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2660
2717
  );
2661
2718
  }
2662
2719
 
@@ -2666,15 +2723,31 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2666
2723
  return [ out, pointer ];
2667
2724
  };
2668
2725
 
2669
- const makeString = (scope, str, global = false, name = '$undeclared') => {
2726
+ const byteStringable = str => {
2727
+ if (!process.argv.includes('-bytestring')) return false;
2728
+
2729
+ for (let i = 0; i < str.length; i++) {
2730
+ if (str.charCodeAt(i) > 0xFF) return false;
2731
+ }
2732
+
2733
+ return true;
2734
+ };
2735
+
2736
+ const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
2670
2737
  const rawElements = new Array(str.length);
2738
+ let byteStringable = process.argv.includes('-bytestring');
2671
2739
  for (let i = 0; i < str.length; i++) {
2672
- rawElements[i] = str.charCodeAt(i);
2740
+ const c = str.charCodeAt(i);
2741
+ rawElements[i] = c;
2742
+
2743
+ if (byteStringable && c > 0xFF) byteStringable = false;
2673
2744
  }
2674
2745
 
2746
+ if (byteStringable && forceBytestring === false) byteStringable = false;
2747
+
2675
2748
  return makeArray(scope, {
2676
2749
  rawElements
2677
- }, global, name, false, 'i16')[0];
2750
+ }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2678
2751
  };
2679
2752
 
2680
2753
  let arrays = new Map();
@@ -2702,10 +2775,13 @@ export const generateMember = (scope, decl, _global, _name) => {
2702
2775
  ];
2703
2776
  }
2704
2777
 
2778
+ const object = generate(scope, decl.object);
2779
+ const property = generate(scope, decl.property);
2780
+
2705
2781
  // // todo: we should only do this for strings but we don't know at compile-time :(
2706
2782
  // hack: this is naughty and will break things!
2707
2783
  let newOut = number(0, valtypeBinary), newPointer = -1;
2708
- if (pages.hasString) {
2784
+ if (pages.hasAnyString) {
2709
2785
  0, [ newOut, newPointer ] = makeArray(scope, {
2710
2786
  rawElements: new Array(1)
2711
2787
  }, _global, _name, true, 'i16');
@@ -2714,7 +2790,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2714
2790
  return typeSwitch(scope, getNodeType(scope, decl.object), {
2715
2791
  [TYPES._array]: [
2716
2792
  // get index as valtype
2717
- ...generate(scope, decl.property),
2793
+ ...property,
2718
2794
 
2719
2795
  // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2720
2796
  Opcodes.i32_to_u,
@@ -2722,7 +2798,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2722
2798
  [ Opcodes.i32_mul ],
2723
2799
 
2724
2800
  ...(aotPointer ? [] : [
2725
- ...generate(scope, decl.object),
2801
+ ...object,
2726
2802
  Opcodes.i32_to_u,
2727
2803
  [ Opcodes.i32_add ]
2728
2804
  ]),
@@ -2741,14 +2817,14 @@ export const generateMember = (scope, decl, _global, _name) => {
2741
2817
 
2742
2818
  ...number(0, Valtype.i32), // base 0 for store later
2743
2819
 
2744
- ...generate(scope, decl.property),
2745
-
2820
+ ...property,
2746
2821
  Opcodes.i32_to_u,
2822
+
2747
2823
  ...number(ValtypeSize.i16, Valtype.i32),
2748
2824
  [ Opcodes.i32_mul ],
2749
2825
 
2750
2826
  ...(aotPointer ? [] : [
2751
- ...generate(scope, decl.object),
2827
+ ...object,
2752
2828
  Opcodes.i32_to_u,
2753
2829
  [ Opcodes.i32_add ]
2754
2830
  ]),
@@ -2765,8 +2841,36 @@ export const generateMember = (scope, decl, _global, _name) => {
2765
2841
  ...number(TYPES.string, Valtype.i32),
2766
2842
  setLastType(scope)
2767
2843
  ],
2844
+ [TYPES._bytestring]: [
2845
+ // setup new/out array
2846
+ ...newOut,
2847
+ [ Opcodes.drop ],
2848
+
2849
+ ...number(0, Valtype.i32), // base 0 for store later
2850
+
2851
+ ...property,
2852
+ Opcodes.i32_to_u,
2853
+
2854
+ ...(aotPointer ? [] : [
2855
+ ...object,
2856
+ Opcodes.i32_to_u,
2857
+ [ Opcodes.i32_add ]
2858
+ ]),
2859
+
2860
+ // load current string ind {arg}
2861
+ [ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2768
2862
 
2769
- default: [ [ Opcodes.unreachable ] ]
2863
+ // store to new string ind 0
2864
+ [ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2865
+
2866
+ // return new string (page)
2867
+ ...number(newPointer),
2868
+
2869
+ ...number(TYPES._bytestring, Valtype.i32),
2870
+ setLastType(scope)
2871
+ ],
2872
+
2873
+ default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
2770
2874
  });
2771
2875
  };
2772
2876
 
@@ -2783,11 +2887,14 @@ const objectHack = node => {
2783
2887
  // if object is not identifier or another member exp, give up
2784
2888
  if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
2785
2889
 
2786
- if (!objectName) objectName = objectHack(node.object).name.slice(2);
2890
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2787
2891
 
2788
2892
  // if .length, give up (hack within a hack!)
2789
2893
  if (node.property.name === 'length') return node;
2790
2894
 
2895
+ // no object name, give up
2896
+ if (!objectName) return node;
2897
+
2791
2898
  const name = '__' + objectName + '_' + node.property.name;
2792
2899
  if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
2793
2900
 
@@ -2846,10 +2953,8 @@ const generateFunc = (scope, decl) => {
2846
2953
  const func = {
2847
2954
  name,
2848
2955
  params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
2849
- returns: innerScope.returns,
2850
- locals: innerScope.locals,
2851
- throws: innerScope.throws,
2852
- index: currentFuncIndex++
2956
+ index: currentFuncIndex++,
2957
+ ...innerScope
2853
2958
  };
2854
2959
  funcIndex[name] = func.index;
2855
2960
 
@@ -2887,6 +2992,16 @@ const generateCode = (scope, decl) => {
2887
2992
  };
2888
2993
 
2889
2994
  const internalConstrs = {
2995
+ Boolean: {
2996
+ generate: (scope, decl) => {
2997
+ if (decl.arguments.length === 0) return number(0);
2998
+
2999
+ // should generate/run all args
3000
+ return truthy(scope, generate(scope, decl.arguments[0]), getNodeType(scope, decl.arguments[0]), false, false);
3001
+ },
3002
+ type: TYPES.boolean
3003
+ },
3004
+
2890
3005
  Array: {
2891
3006
  generate: (scope, decl, global, name) => {
2892
3007
  // new Array(i0, i1, ...)