porffor 0.0.0-bddcdc3 → 0.0.0-d650361

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.
@@ -5,6 +5,7 @@ import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from "./bui
5
5
  import { PrototypeFuncs } from "./prototype.js";
6
6
  import { number, i32x4 } from "./embedding.js";
7
7
  import parse from "./parse.js";
8
+ import * as Rhemyn from "../rhemyn/compile.js";
8
9
 
9
10
  let globals = {};
10
11
  let globalInd = 0;
@@ -35,7 +36,14 @@ const debug = str => {
35
36
  };
36
37
 
37
38
  const todo = msg => {
38
- throw new Error(`todo: ${msg}`);
39
+ class TodoError extends Error {
40
+ constructor(message) {
41
+ super(message);
42
+ this.name = 'TodoError';
43
+ }
44
+ }
45
+
46
+ throw new TodoError(`todo: ${msg}`);
39
47
 
40
48
  const code = [];
41
49
 
@@ -101,8 +109,8 @@ const generate = (scope, decl, global = false, name = undefined) => {
101
109
  case 'WhileStatement':
102
110
  return generateWhile(scope, decl);
103
111
 
104
- /* case 'ForOfStatement':
105
- return generateForOf(scope, decl); */
112
+ case 'ForOfStatement':
113
+ return generateForOf(scope, decl);
106
114
 
107
115
  case 'BreakStatement':
108
116
  return generateBreak(scope, decl);
@@ -144,45 +152,65 @@ const generate = (scope, decl, global = false, name = undefined) => {
144
152
 
145
153
  return [];
146
154
 
147
- case 'TaggedTemplateExpression':
148
- // hack for inline asm
149
- if (decl.tag.name !== 'asm') return todo('tagged template expressions not implemented');
155
+ case 'TaggedTemplateExpression': {
156
+ const funcs = {
157
+ asm: str => {
158
+ let out = [];
150
159
 
151
- const str = decl.quasi.quasis[0].value.raw;
152
- let out = [];
160
+ for (const line of str.split('\n')) {
161
+ const asm = line.trim().split(';;')[0].split(' ');
162
+ if (asm[0] === '') continue; // blank
153
163
 
154
- for (const line of str.split('\n')) {
155
- const asm = line.trim().split(';;')[0].split(' ');
156
- if (asm[0] === '') continue; // blank
164
+ if (asm[0] === 'local') {
165
+ const [ name, idx, type ] = asm.slice(1);
166
+ scope.locals[name] = { idx: parseInt(idx), type: Valtype[type] };
167
+ continue;
168
+ }
157
169
 
158
- if (asm[0] === 'local') {
159
- const [ name, idx, type ] = asm.slice(1);
160
- scope.locals[name] = { idx: parseInt(idx), type: Valtype[type] };
161
- continue;
162
- }
170
+ if (asm[0] === 'returns') {
171
+ scope.returns = asm.slice(1).map(x => Valtype[x]);
172
+ continue;
173
+ }
163
174
 
164
- if (asm[0] === 'returns') {
165
- scope.returns = asm.slice(1).map(x => Valtype[x]);
166
- continue;
167
- }
175
+ if (asm[0] === 'memory') {
176
+ allocPage('asm instrinsic');
177
+ // todo: add to store/load offset insts
178
+ continue;
179
+ }
168
180
 
169
- if (asm[0] === 'memory') {
170
- scope.memory = true;
171
- allocPage('asm instrinsic');
172
- // todo: add to store/load offset insts
173
- continue;
174
- }
181
+ let inst = Opcodes[asm[0].replace('.', '_')];
182
+ if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
183
+
184
+ if (!Array.isArray(inst)) inst = [ inst ];
185
+ const immediates = asm.slice(1).map(x => parseInt(x));
186
+
187
+ out.push([ ...inst, ...immediates ]);
188
+ }
175
189
 
176
- let inst = Opcodes[asm[0].replace('.', '_')];
177
- if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
190
+ return out;
191
+ },
178
192
 
179
- if (!Array.isArray(inst)) inst = [ inst ];
180
- const immediates = asm.slice(1).map(x => parseInt(x));
193
+ __internal_print_type: str => {
194
+ const type = getType(scope, str) - TYPES.number;
181
195
 
182
- out.push([ ...inst, ...immediates ]);
196
+ return [
197
+ ...number(type),
198
+ [ Opcodes.call, importedFuncs.print ],
199
+
200
+ // newline
201
+ ...number(10),
202
+ [ Opcodes.call, importedFuncs.printChar ]
203
+ ];
204
+ }
183
205
  }
184
206
 
185
- return out;
207
+ const name = decl.tag.name;
208
+ // hack for inline asm
209
+ if (!funcs[name]) return todo('tagged template expressions not implemented');
210
+
211
+ const str = decl.quasi.quasis[0].value.raw;
212
+ return funcs[name](str);
213
+ }
186
214
 
187
215
  default:
188
216
  return todo(`no generation for ${decl.type}!`);
@@ -281,7 +309,7 @@ const generateReturn = (scope, decl) => {
281
309
  ];
282
310
  }
283
311
 
284
- if (!scope.returnType) scope.returnType = getNodeType(scope, decl.argument);
312
+ scope.returnType = getNodeType(scope, decl.argument);
285
313
 
286
314
  return [
287
315
  ...generate(scope, decl.argument),
@@ -298,11 +326,11 @@ const localTmp = (scope, name, type = valtypeBinary) => {
298
326
  return idx;
299
327
  };
300
328
 
301
- const performLogicOp = (scope, op, left, right) => {
329
+ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
302
330
  const checks = {
303
- '||': Opcodes.eqz,
304
- '&&': [ Opcodes.i32_to ]
305
- // todo: ??
331
+ '||': falsy,
332
+ '&&': truthy,
333
+ '??': nullish
306
334
  };
307
335
 
308
336
  if (!checks[op]) return todo(`logic operator ${op} not implemented yet`);
@@ -313,7 +341,8 @@ const performLogicOp = (scope, op, left, right) => {
313
341
  return [
314
342
  ...left,
315
343
  [ Opcodes.local_tee, localTmp(scope, 'logictmp') ],
316
- ...checks[op],
344
+ ...checks[op](scope, [], leftType),
345
+ Opcodes.i32_to,
317
346
  [ Opcodes.if, valtypeBinary ],
318
347
  ...right,
319
348
  [ Opcodes.else ],
@@ -328,8 +357,6 @@ const concatStrings = (scope, left, right, global, name, assign) => {
328
357
  // todo: optimize by looking up names in arrays and using that if exists?
329
358
  // todo: optimize this if using literals/known lengths?
330
359
 
331
- scope.memory = true;
332
-
333
360
  const rightPointer = localTmp(scope, 'concat_right_pointer', Valtype.i32);
334
361
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
335
362
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
@@ -464,8 +491,6 @@ const compareStrings = (scope, left, right) => {
464
491
  // todo: optimize by looking up names in arrays and using that if exists?
465
492
  // todo: optimize this if using literals/known lengths?
466
493
 
467
- scope.memory = true;
468
-
469
494
  const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
470
495
  const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
471
496
  const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
@@ -563,102 +588,142 @@ const compareStrings = (scope, left, right) => {
563
588
  ];
564
589
  };
565
590
 
566
- const falsy = (scope, wasm, type) => {
591
+ const truthy = (scope, wasm, type) => {
567
592
  // arrays are always truthy
568
593
  if (type === TYPES._array) return [
569
594
  ...wasm,
570
595
  [ Opcodes.drop ],
571
- number(0)
596
+ ...number(1)
572
597
  ];
573
598
 
574
599
  if (type === TYPES.string) {
575
- // if "" (length = 0)
600
+ // if not "" (length = 0)
576
601
  return [
577
602
  // pointer
578
603
  ...wasm,
604
+ Opcodes.i32_to_u,
579
605
 
580
606
  // get length
581
607
  [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
582
608
 
583
- // if length == 0
584
- [ Opcodes.i32_eqz ],
609
+ // if length != 0
610
+ /* [ Opcodes.i32_eqz ],
611
+ [ Opcodes.i32_eqz ], */
585
612
  Opcodes.i32_from_u
586
613
  ]
587
614
  }
588
615
 
589
- // if = 0
616
+ // if != 0
590
617
  return [
591
618
  ...wasm,
592
619
 
593
- ...Opcodes.eqz,
594
- Opcodes.i32_from_u
620
+ /* Opcodes.eqz,
621
+ [ Opcodes.i32_eqz ],
622
+ Opcodes.i32_from */
595
623
  ];
596
624
  };
597
625
 
598
- const truthy = (scope, wasm, type) => {
626
+ const falsy = (scope, wasm, type) => {
599
627
  // arrays are always truthy
600
628
  if (type === TYPES._array) return [
601
629
  ...wasm,
602
630
  [ Opcodes.drop ],
603
- number(1)
631
+ ...number(0)
604
632
  ];
605
633
 
606
634
  if (type === TYPES.string) {
607
- // if not "" (length = 0)
635
+ // if "" (length = 0)
608
636
  return [
609
637
  // pointer
610
638
  ...wasm,
639
+ Opcodes.i32_to_u,
611
640
 
612
641
  // get length
613
642
  [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
614
643
 
615
- // if length != 0
616
- /* [ Opcodes.i32_eqz ],
617
- [ Opcodes.i32_eqz ], */
644
+ // if length == 0
645
+ [ Opcodes.i32_eqz ],
618
646
  Opcodes.i32_from_u
619
647
  ]
620
648
  }
621
649
 
622
- // if != 0
650
+ // if = 0
623
651
  return [
624
652
  ...wasm,
625
653
 
626
- /* Opcodes.eqz,
627
- [ Opcodes.i32_eqz ],
628
- Opcodes.i32_from */
654
+ ...Opcodes.eqz,
655
+ Opcodes.i32_from_u
656
+ ];
657
+ };
658
+
659
+ const nullish = (scope, wasm, type) => {
660
+ // undefined
661
+ if (type === TYPES.undefined) return [
662
+ ...wasm,
663
+ [ Opcodes.drop ],
664
+ ...number(1)
665
+ ];
666
+
667
+ // null (if object and = "0")
668
+ if (type === TYPES.object) return [
669
+ ...wasm,
670
+ ...Opcodes.eqz,
671
+ Opcodes.i32_from_u
672
+ ];
673
+
674
+ // not
675
+ return [
676
+ ...wasm,
677
+ [ Opcodes.drop ],
678
+ ...number(0)
629
679
  ];
630
680
  };
631
681
 
632
682
  const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$undeclared', assign = false) => {
633
683
  if (op === '||' || op === '&&' || op === '??') {
634
- return performLogicOp(scope, op, left, right);
684
+ return performLogicOp(scope, op, left, right, leftType, rightType);
635
685
  }
636
686
 
637
687
  if (codeLog && (!leftType || !rightType)) log('codegen', 'untracked type in op', op, _name, '\n' + new Error().stack.split('\n').slice(1).join('\n'));
638
688
 
639
- // if strict (in)equal and known types mismatch, return false (===)/true (!==)
640
- if ((op === '===' || op === '!==') && leftType && rightType && leftType !== rightType) {
689
+ const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
690
+
691
+ if (leftType && rightType && (
692
+ // if strict (in)equal and known types mismatch, return false (===)/true (!==)
693
+ ((op === '===' || op === '!==') && leftType !== rightType) ||
694
+
695
+ // if equality op and an operand is undefined, return false
696
+ (eqOp && leftType === TYPES.undefined ^ rightType === TYPES.undefined)
697
+ )) {
698
+ // undefined == null
699
+ if (((leftType === TYPES.undefined && rightType === TYPES.object) || (leftType === TYPES.object && rightType === TYPES.undefined)) && (op === '==' || op === '!=')) return [
700
+ ...(leftType === TYPES.object ? left : right),
701
+ ...Opcodes.eqz,
702
+ ...(op === '!=' ? [ [ Opcodes.i32_eqz ] ] : [])
703
+ ];
704
+
641
705
  return [
642
706
  ...left,
643
- ...right,
644
-
645
- // drop values
646
707
  [ Opcodes.drop ],
708
+
709
+ ...right,
647
710
  [ Opcodes.drop ],
648
711
 
649
- // return false (===)/true (!==)
650
- ...number(op === '===' ? 0 : 1, Valtype.i32)
712
+ // return true (!=/!==) or false (else)
713
+ ...number(op === '!=' || op === '!==' ? 1 : 0, Valtype.i32)
651
714
  ];
652
715
  }
653
716
 
717
+ // todo: niche null hell with 0
718
+
654
719
  if (leftType === TYPES.string || rightType === TYPES.string) {
655
720
  if (op === '+') {
656
721
  // string concat (a + b)
657
722
  return concatStrings(scope, left, right, _global, _name, assign);
658
723
  }
659
724
 
660
- // any other math op, NaN
661
- if (!['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op)) return number(NaN);
725
+ // not an equality op, NaN
726
+ if (!eqOp) return number(NaN);
662
727
 
663
728
  // else leave bool ops
664
729
  // todo: convert string to number if string and number/bool
@@ -703,21 +768,15 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
703
768
  ];
704
769
  };
705
770
 
706
- let binaryExpDepth = 0;
707
771
  const generateBinaryExp = (scope, decl, _global, _name) => {
708
- binaryExpDepth++;
709
-
710
- const out = [
711
- ...performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name)
712
- ];
772
+ const out = performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name);
713
773
 
714
774
  if (valtype !== 'i32' && ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(decl.operator)) out.push(Opcodes.i32_from_u);
715
775
 
716
- binaryExpDepth--;
717
776
  return out;
718
777
  };
719
778
 
720
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, memory, localNames = [], globalNames = [] }) => {
779
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [] }) => {
721
780
  const existing = funcs.find(x => x.name === name);
722
781
  if (existing) return existing;
723
782
 
@@ -753,7 +812,6 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
753
812
  returns,
754
813
  returnType: TYPES[returnType ?? 'number'],
755
814
  wasm,
756
- memory,
757
815
  internal: true,
758
816
  index: currentFuncIndex++
759
817
  };
@@ -772,7 +830,7 @@ const includeBuiltin = (scope, builtin) => {
772
830
  };
773
831
 
774
832
  const generateLogicExp = (scope, decl) => {
775
- return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right));
833
+ return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
776
834
  };
777
835
 
778
836
  const TYPES = {
@@ -786,7 +844,8 @@ const TYPES = {
786
844
  bigint: 0xffffffffffff7,
787
845
 
788
846
  // these are not "typeof" types but tracked internally
789
- _array: 0xffffffffffff8
847
+ _array: 0xfffffffffff0f,
848
+ _regexp: 0xfffffffffff1f
790
849
  };
791
850
 
792
851
  const TYPE_NAMES = {
@@ -821,6 +880,8 @@ const getType = (scope, _name) => {
821
880
  const getNodeType = (scope, node) => {
822
881
  if (node.type === 'Literal') {
823
882
  if (['number', 'boolean', 'string', 'undefined', 'object', 'function', 'symbol', 'bigint'].includes(node.value)) return TYPES.number;
883
+ if (node.regex) return TYPES._regexp;
884
+
824
885
  return TYPES[typeof node.value];
825
886
  }
826
887
 
@@ -854,6 +915,11 @@ const getNodeType = (scope, node) => {
854
915
 
855
916
  // literal.func()
856
917
  if (!name && node.callee.type === 'MemberExpression') {
918
+ if (node.callee.object.regex) {
919
+ const funcName = node.callee.property.name;
920
+ return Rhemyn[funcName] ? TYPES.boolean : TYPES.undefined;
921
+ }
922
+
857
923
  const baseType = getNodeType(scope, node.callee.object);
858
924
 
859
925
  const func = node.callee.property.name;
@@ -897,6 +963,11 @@ const getNodeType = (scope, node) => {
897
963
  const generateLiteral = (scope, decl, global, name) => {
898
964
  if (decl.value === null) return number(NULL);
899
965
 
966
+ if (decl.regex) {
967
+ scope.regex[name] = decl.regex;
968
+ return number(1);
969
+ }
970
+
900
971
  switch (typeof decl.value) {
901
972
  case 'number':
902
973
  return number(decl.value);
@@ -936,7 +1007,8 @@ const generateLiteral = (scope, decl, global, name) => {
936
1007
  const countLeftover = wasm => {
937
1008
  let count = 0, depth = 0;
938
1009
 
939
- for (const inst of wasm) {
1010
+ for (let i = 0; i < wasm.length; i++) {
1011
+ const inst = wasm[i];
940
1012
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
941
1013
  if (inst[0] === Opcodes.if) count--;
942
1014
  if (inst[1] !== Blocktype.void) count++;
@@ -957,6 +1029,8 @@ const countLeftover = wasm => {
957
1029
  } else count--;
958
1030
  if (func) count += func.returns.length;
959
1031
  } else count--;
1032
+
1033
+ // console.log(count, decompile([ inst ]).slice(0, -1));
960
1034
  }
961
1035
 
962
1036
  return count;
@@ -1040,7 +1114,7 @@ const generateCall = (scope, decl, _global, _name) => {
1040
1114
  }
1041
1115
 
1042
1116
  let out = [];
1043
- let protoFunc, protoName, baseType, baseName = '$undeclared';
1117
+ let protoFunc, protoName, baseType, baseName;
1044
1118
  // ident.func()
1045
1119
  if (name && name.startsWith('__')) {
1046
1120
  const spl = name.slice(2).split('_');
@@ -1055,6 +1129,25 @@ const generateCall = (scope, decl, _global, _name) => {
1055
1129
 
1056
1130
  // literal.func()
1057
1131
  if (!name && decl.callee.type === 'MemberExpression') {
1132
+ // megahack for /regex/.func()
1133
+ if (decl.callee.object.regex) {
1134
+ const funcName = decl.callee.property.name;
1135
+ const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1136
+
1137
+ funcIndex[func.name] = func.index;
1138
+ funcs.push(func);
1139
+
1140
+ return [
1141
+ // make string arg
1142
+ ...generate(scope, decl.arguments[0]),
1143
+
1144
+ // call regex func
1145
+ Opcodes.i32_to_u,
1146
+ [ Opcodes.call, func.index ],
1147
+ Opcodes.i32_from
1148
+ ];
1149
+ }
1150
+
1058
1151
  baseType = getNodeType(scope, decl.callee.object);
1059
1152
 
1060
1153
  const func = decl.callee.property.name;
@@ -1063,11 +1156,36 @@ const generateCall = (scope, decl, _global, _name) => {
1063
1156
 
1064
1157
  out = generate(scope, decl.callee.object);
1065
1158
  out.push([ Opcodes.drop ]);
1159
+
1160
+ baseName = [...arrays.keys()].pop();
1066
1161
  }
1067
1162
 
1068
- if (protoFunc) {
1069
- scope.memory = true;
1163
+ if (protoName && baseType === TYPES.string && Rhemyn[protoName]) {
1164
+ const func = Rhemyn[protoName](decl.arguments[0].regex.pattern, currentFuncIndex++);
1165
+
1166
+ funcIndex[func.name] = func.index;
1167
+ funcs.push(func);
1168
+
1169
+ const pointer = arrays.get(baseName);
1170
+ const [ local, isGlobal ] = lookupName(scope, baseName);
1171
+
1172
+ return [
1173
+ ...out,
1174
+
1175
+ ...(pointer == null ? [
1176
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1177
+ Opcodes.i32_to_u,
1178
+ ] : [
1179
+ ...number(pointer, Valtype.i32)
1180
+ ]),
1181
+
1182
+ // call regex func
1183
+ [ Opcodes.call, func.index ],
1184
+ Opcodes.i32_from
1185
+ ];
1186
+ }
1070
1187
 
1188
+ if (protoFunc) {
1071
1189
  let pointer = arrays.get(baseName);
1072
1190
 
1073
1191
  if (pointer == null) {
@@ -1075,7 +1193,7 @@ const generateCall = (scope, decl, _global, _name) => {
1075
1193
  if (codeLog) log('codegen', 'cloning unknown dynamic pointer');
1076
1194
 
1077
1195
  // register array
1078
- const [ , pointer ] = makeArray(scope, {
1196
+ 0, [ , pointer ] = makeArray(scope, {
1079
1197
  rawElements: new Array(0)
1080
1198
  }, _global, baseName, true, baseType === TYPES.string ? 'i16' : valtype);
1081
1199
 
@@ -1107,9 +1225,10 @@ const generateCall = (scope, decl, _global, _name) => {
1107
1225
 
1108
1226
  [ Opcodes.block, valtypeBinary ],
1109
1227
  ...protoFunc(pointer, {
1110
- cachedI32: [ [ Opcodes.local_get, lengthLocal ] ],
1111
- get: arrayUtil.getLength(pointer),
1112
- getI32: arrayUtil.getLengthI32(pointer),
1228
+ getCachedI32: () => [ [ Opcodes.local_get, lengthLocal ] ],
1229
+ setCachedI32: () => [ [ Opcodes.local_set, lengthLocal ] ],
1230
+ get: () => arrayUtil.getLength(pointer),
1231
+ getI32: () => arrayUtil.getLengthI32(pointer),
1113
1232
  set: value => arrayUtil.setLength(pointer, value),
1114
1233
  setI32: value => arrayUtil.setLengthI32(pointer, value)
1115
1234
  }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, (length, itemType) => {
@@ -1173,11 +1292,10 @@ const generateCall = (scope, decl, _global, _name) => {
1173
1292
  args = args.slice(0, func.params.length);
1174
1293
  }
1175
1294
 
1176
- if (func && func.memory) scope.memory = true;
1177
1295
  if (func && func.throws) scope.throws = true;
1178
1296
 
1179
1297
  for (const arg of args) {
1180
- out.push(...generate(scope, arg));
1298
+ out = out.concat(generate(scope, arg));
1181
1299
  }
1182
1300
 
1183
1301
  out.push([ Opcodes.call, idx ]);
@@ -1189,7 +1307,7 @@ const generateNew = (scope, decl, _global, _name) => {
1189
1307
  // hack: basically treat this as a normal call for builtins for now
1190
1308
  const name = mapName(decl.callee.name);
1191
1309
  if (internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
1192
- if (!builtinFuncs[name]) return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1310
+ if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1193
1311
 
1194
1312
  return generateCall(scope, decl, _global, _name);
1195
1313
  };
@@ -1297,8 +1415,6 @@ const generateAssign = (scope, decl) => {
1297
1415
  const name = decl.left.object.name;
1298
1416
  const pointer = arrays.get(name);
1299
1417
 
1300
- scope.memory = true;
1301
-
1302
1418
  const aotPointer = pointer != null;
1303
1419
 
1304
1420
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
@@ -1319,6 +1435,47 @@ const generateAssign = (scope, decl) => {
1319
1435
  ];
1320
1436
  }
1321
1437
 
1438
+ if (decl.left.type === 'MemberExpression' && decl.left.computed) {
1439
+ // arr[i] | str[i]
1440
+ const name = decl.left.object.name;
1441
+ const pointer = arrays.get(name);
1442
+
1443
+ const aotPointer = pointer != null;
1444
+
1445
+ const newValueTmp = localTmp(scope, '__member_setter_tmp');
1446
+
1447
+ const parentType = getNodeType(scope, decl.left.object);
1448
+
1449
+ const op = decl.operator.slice(0, -1);
1450
+ return [
1451
+ ...(aotPointer ? number(0, Valtype.i32) : [
1452
+ ...generate(scope, decl.left.object),
1453
+ Opcodes.i32_to_u
1454
+ ]),
1455
+
1456
+ // get index as valtype
1457
+ ...generate(scope, decl.left.property),
1458
+
1459
+ // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
1460
+ Opcodes.i32_to_u,
1461
+ ...number(ValtypeSize[valtype], Valtype.i32),
1462
+ [ Opcodes.i32_mul ],
1463
+ [ Opcodes.i32_add ],
1464
+
1465
+ ...(op === '' ? generate(scope, decl.right, false, name) : performOp(scope, op, generate(scope, decl.left), generate(scope, decl.right), parentType === TYPES._array ? TYPES.number : TYPES.string, getNodeType(scope, decl.right), false, name, true)),
1466
+ [ Opcodes.local_tee, newValueTmp ],
1467
+
1468
+ ...(parentType === TYPES._array ? [
1469
+ [ Opcodes.store, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1470
+ ] : [
1471
+ Opcodes.i32_to_u,
1472
+ [ StoreOps.i16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1473
+ ]),
1474
+
1475
+ [ Opcodes.local_get, newValueTmp ]
1476
+ ];
1477
+ }
1478
+
1322
1479
  const [ local, isGlobal ] = lookupName(scope, name);
1323
1480
 
1324
1481
  if (local === undefined) {
@@ -1339,8 +1496,10 @@ const generateAssign = (scope, decl) => {
1339
1496
  ];
1340
1497
  }
1341
1498
 
1499
+ typeStates[name] = getNodeType(scope, decl.right);
1500
+
1342
1501
  if (decl.operator === '=') {
1343
- typeStates[name] = getNodeType(scope, decl.right);
1502
+ // typeStates[name] = getNodeType(scope, decl.right);
1344
1503
 
1345
1504
  return [
1346
1505
  ...generate(scope, decl.right, isGlobal, name),
@@ -1349,8 +1508,27 @@ const generateAssign = (scope, decl) => {
1349
1508
  ];
1350
1509
  }
1351
1510
 
1511
+ const op = decl.operator.slice(0, -1);
1512
+ if (op === '||' || op === '&&' || op === '??') {
1513
+ // todo: is this needed?
1514
+ // for logical assignment ops, it is not left @= right ~= left = left @ right
1515
+ // instead, left @ (left = right)
1516
+ // eg, x &&= y ~= x && (x = y)
1517
+
1518
+ return [
1519
+ ...performOp(scope, op, [
1520
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1521
+ ], [
1522
+ ...generate(scope, decl.right),
1523
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
1524
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1525
+ ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
1526
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1527
+ ];
1528
+ }
1529
+
1352
1530
  return [
1353
- ...performOp(scope, decl.operator.slice(0, -1), [ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ] ], generate(scope, decl.right), getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
1531
+ ...performOp(scope, op, [ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ] ], generate(scope, decl.right), getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
1354
1532
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
1355
1533
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1356
1534
  ];
@@ -1377,13 +1555,14 @@ const generateUnary = (scope, decl) => {
1377
1555
 
1378
1556
  case '!':
1379
1557
  // !=
1380
- return falsy(scope, generate(scope, decl.argument));
1558
+ return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument));
1381
1559
 
1382
1560
  case '~':
1561
+ // todo: does not handle Infinity properly (should convert to 0) (but opt const converting saves us sometimes)
1383
1562
  return [
1384
1563
  ...generate(scope, decl.argument),
1385
1564
  Opcodes.i32_to,
1386
- [ Opcodes.i32_const, signedLEB128(-1) ],
1565
+ [ Opcodes.i32_const, ...signedLEB128(-1) ],
1387
1566
  [ Opcodes.i32_xor ],
1388
1567
  Opcodes.i32_from
1389
1568
  ];
@@ -1464,7 +1643,7 @@ const generateUpdate = (scope, decl) => {
1464
1643
  };
1465
1644
 
1466
1645
  const generateIf = (scope, decl) => {
1467
- const out = truthy(scope, generate(scope, decl.test), decl.test);
1646
+ const out = truthy(scope, generate(scope, decl.test), getNodeType(scope, decl.test));
1468
1647
 
1469
1648
  out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
1470
1649
  depth.push('if');
@@ -1557,18 +1736,106 @@ const generateWhile = (scope, decl) => {
1557
1736
  const generateForOf = (scope, decl) => {
1558
1737
  const out = [];
1559
1738
 
1739
+ const rightType = getNodeType(scope, decl.right);
1740
+ const valtypeSize = rightType === TYPES._array ? ValtypeSize[valtype] : ValtypeSize.i16; // presume array (:()
1741
+
1742
+ // todo: for of inside for of might fuck up?
1743
+ const pointer = localTmp(scope, 'forof_base_pointer', Valtype.i32);
1744
+ const length = localTmp(scope, 'forof_length', Valtype.i32);
1745
+ const counter = localTmp(scope, 'forof_counter', Valtype.i32);
1746
+
1747
+ out.push(
1748
+ // set pointer as right
1749
+ ...generate(scope, decl.right),
1750
+ Opcodes.i32_to_u,
1751
+ [ Opcodes.local_set, pointer ],
1752
+
1753
+ // get length
1754
+ [ Opcodes.local_get, pointer ],
1755
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
1756
+ [ Opcodes.local_set, length ]
1757
+ );
1758
+
1560
1759
  out.push([ Opcodes.loop, Blocktype.void ]);
1561
- depth.push('while');
1760
+ depth.push('forof');
1562
1761
 
1563
- out.push(...generate(scope, decl.test));
1564
- out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
1565
- depth.push('if');
1762
+ // setup local for left
1763
+ generate(scope, decl.left);
1566
1764
 
1567
- out.push(...generate(scope, decl.body));
1765
+ const leftName = decl.left.declarations[0].id.name;
1568
1766
 
1569
- out.push([ Opcodes.br, 1 ]);
1570
- out.push([ Opcodes.end ], [ Opcodes.end ]);
1571
- depth.pop(); depth.pop();
1767
+ // set type for local
1768
+ typeStates[leftName] = rightType === TYPES._array ? TYPES.number : TYPES.string;
1769
+
1770
+ const [ local, isGlobal ] = lookupName(scope, leftName);
1771
+
1772
+ if (rightType === TYPES._array) { // array
1773
+ out.push(
1774
+ [ Opcodes.local_get, pointer ],
1775
+ [ Opcodes.load, Math.log2(valtypeSize) - 1, ...unsignedLEB128(ValtypeSize.i32) ]
1776
+ );
1777
+ } else { // string
1778
+ const [ newOut, newPointer ] = makeArray(scope, {
1779
+ rawElements: new Array(1)
1780
+ }, isGlobal, leftName, true, 'i16');
1781
+
1782
+ out.push(
1783
+ // setup new/out array
1784
+ ...newOut,
1785
+ [ Opcodes.drop ],
1786
+
1787
+ ...number(0, Valtype.i32), // base 0 for store after
1788
+
1789
+ // load current string ind {arg}
1790
+ [ Opcodes.local_get, pointer ],
1791
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
1792
+
1793
+ // store to new string ind 0
1794
+ [ Opcodes.i32_store16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
1795
+
1796
+ // return new string (page)
1797
+ ...number(newPointer)
1798
+ );
1799
+ }
1800
+
1801
+ // set left value
1802
+ out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ]);
1803
+
1804
+ out.push(
1805
+ [ Opcodes.block, Blocktype.void ],
1806
+ [ Opcodes.block, Blocktype.void ]
1807
+ );
1808
+ depth.push('block');
1809
+ depth.push('block');
1810
+
1811
+ out.push(
1812
+ ...generate(scope, decl.body),
1813
+ [ Opcodes.end ]
1814
+ );
1815
+ depth.pop();
1816
+
1817
+ out.push(
1818
+ // increment iter pointer by valtype size
1819
+ [ Opcodes.local_get, pointer ],
1820
+ ...number(valtypeSize, Valtype.i32),
1821
+ [ Opcodes.i32_add ],
1822
+ [ Opcodes.local_set, pointer ],
1823
+
1824
+ // increment counter by 1
1825
+ [ Opcodes.local_get, counter ],
1826
+ ...number(1, Valtype.i32),
1827
+ [ Opcodes.i32_add ],
1828
+ [ Opcodes.local_tee, counter ],
1829
+
1830
+ // loop if counter != length
1831
+ [ Opcodes.local_get, length ],
1832
+ [ Opcodes.i32_ne ],
1833
+ [ Opcodes.br_if, 1 ],
1834
+
1835
+ [ Opcodes.end ], [ Opcodes.end ]
1836
+ );
1837
+ depth.pop();
1838
+ depth.pop();
1572
1839
 
1573
1840
  return out;
1574
1841
  };
@@ -1659,13 +1926,22 @@ const generateAssignPat = (scope, decl) => {
1659
1926
  };
1660
1927
 
1661
1928
  let pages = new Map();
1662
- const allocPage = reason => {
1663
- if (pages.has(reason)) return pages.get(reason);
1929
+ const allocPage = (reason, type) => {
1930
+ if (pages.has(reason)) return pages.get(reason).ind;
1664
1931
 
1665
- let ind = pages.size;
1666
- pages.set(reason, ind);
1932
+ const ind = pages.size;
1933
+ pages.set(reason, { ind, type });
1667
1934
 
1668
- if (codeLog) log('codegen', `allocated new page of memory (${ind}) | ${reason}`);
1935
+ if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
1936
+
1937
+ return ind;
1938
+ };
1939
+
1940
+ const freePage = reason => {
1941
+ const { ind } = pages.get(reason);
1942
+ pages.delete(reason);
1943
+
1944
+ if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
1669
1945
 
1670
1946
  return ind;
1671
1947
  };
@@ -1679,7 +1955,7 @@ const itemTypeToValtype = {
1679
1955
  i16: 'i32'
1680
1956
  };
1681
1957
 
1682
- const storeOps = {
1958
+ const StoreOps = {
1683
1959
  i32: Opcodes.i32_store,
1684
1960
  i64: Opcodes.i64_store,
1685
1961
  f64: Opcodes.f64_store,
@@ -1694,7 +1970,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1694
1970
  if (!arrays.has(name) || name === '$undeclared') {
1695
1971
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
1696
1972
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
1697
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`) * pageSize);
1973
+ arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
1698
1974
  }
1699
1975
 
1700
1976
  const pointer = arrays.get(name);
@@ -1711,7 +1987,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1711
1987
  [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
1712
1988
  );
1713
1989
 
1714
- const storeOp = storeOps[itemType];
1990
+ const storeOp = StoreOps[itemType];
1715
1991
  const valtype = itemTypeToValtype[itemType];
1716
1992
 
1717
1993
  if (!initEmpty) for (let i = 0; i < length; i++) {
@@ -1727,8 +2003,6 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1727
2003
  // local value as pointer
1728
2004
  out.push(...number(pointer));
1729
2005
 
1730
- scope.memory = true;
1731
-
1732
2006
  return [ out, pointer ];
1733
2007
  };
1734
2008
 
@@ -1747,8 +2021,6 @@ export const generateMember = (scope, decl, _global, _name) => {
1747
2021
  const name = decl.object.name;
1748
2022
  const pointer = arrays.get(name);
1749
2023
 
1750
- scope.memory = true;
1751
-
1752
2024
  const aotPointer = pointer != null;
1753
2025
 
1754
2026
  return [
@@ -1768,8 +2040,6 @@ export const generateMember = (scope, decl, _global, _name) => {
1768
2040
  const name = decl.object.name;
1769
2041
  const pointer = arrays.get(name);
1770
2042
 
1771
- scope.memory = true;
1772
-
1773
2043
  const aotPointer = pointer != null;
1774
2044
 
1775
2045
  if (type === TYPES._array) {
@@ -1879,7 +2149,7 @@ const generateFunc = (scope, decl) => {
1879
2149
  locals: {},
1880
2150
  localInd: 0,
1881
2151
  returns: [ valtypeBinary ],
1882
- memory: false,
2152
+ returnType: null,
1883
2153
  throws: false,
1884
2154
  name
1885
2155
  };
@@ -1905,7 +2175,6 @@ const generateFunc = (scope, decl) => {
1905
2175
  returns: innerScope.returns,
1906
2176
  returnType: innerScope.returnType,
1907
2177
  locals: innerScope.locals,
1908
- memory: innerScope.memory,
1909
2178
  throws: innerScope.throws,
1910
2179
  index: currentFuncIndex++
1911
2180
  };
@@ -1920,6 +2189,8 @@ const generateFunc = (scope, decl) => {
1920
2189
 
1921
2190
  if (name !== 'main' && func.returns.length !== 0 && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
1922
2191
  wasm.push(...number(0), [ Opcodes.return ]);
2192
+
2193
+ if (func.returnType === null) func.returnType = TYPES.undefined;
1923
2194
  }
1924
2195
 
1925
2196
  // change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
@@ -1930,9 +2201,7 @@ const generateFunc = (scope, decl) => {
1930
2201
  if (local.type === Valtype.v128) {
1931
2202
  vecParams++;
1932
2203
 
1933
- /* func.memory = true; // mark func as using memory
1934
-
1935
- wasm.unshift( // add v128 load for param
2204
+ /* wasm.unshift( // add v128 load for param
1936
2205
  [ Opcodes.i32_const, 0 ],
1937
2206
  [ ...Opcodes.v128_load, 0, i * 16 ],
1938
2207
  [ Opcodes.local_set, local.idx ]
@@ -2043,10 +2312,10 @@ const generateFunc = (scope, decl) => {
2043
2312
  };
2044
2313
 
2045
2314
  const generateCode = (scope, decl) => {
2046
- const out = [];
2315
+ let out = [];
2047
2316
 
2048
2317
  for (const x of decl.body) {
2049
- out.push(...generate(scope, x));
2318
+ out = out.concat(generate(scope, x));
2050
2319
  }
2051
2320
 
2052
2321
  return out;