porffor 0.2.0-50b82f8 → 0.2.0-5ac7ea0

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";
@@ -7,6 +7,7 @@ import { number, i32x4, enforceOneByte, enforceTwoBytes, enforceFourBytes, enfor
7
7
  import { log } from "./log.js";
8
8
  import parse from "./parse.js";
9
9
  import * as Rhemyn from "../rhemyn/compile.js";
10
+ import Prefs from './prefs.js';
10
11
 
11
12
  let globals = {};
12
13
  let globalInd = 0;
@@ -55,7 +56,7 @@ const todo = msg => {
55
56
  };
56
57
 
57
58
  const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
58
- const generate = (scope, decl, global = false, name = undefined) => {
59
+ const generate = (scope, decl, global = false, name = undefined, valueUnused = false) => {
59
60
  switch (decl.type) {
60
61
  case 'BinaryExpression':
61
62
  return generateBinaryExp(scope, decl, global, name);
@@ -68,7 +69,12 @@ const generate = (scope, decl, global = false, name = undefined) => {
68
69
 
69
70
  case 'ArrowFunctionExpression':
70
71
  case 'FunctionDeclaration':
71
- generateFunc(scope, decl);
72
+ const func = generateFunc(scope, decl);
73
+
74
+ if (decl.type.endsWith('Expression')) {
75
+ return number(func.index);
76
+ }
77
+
72
78
  return [];
73
79
 
74
80
  case 'BlockStatement':
@@ -81,7 +87,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
81
87
  return generateExp(scope, decl);
82
88
 
83
89
  case 'CallExpression':
84
- return generateCall(scope, decl, global, name);
90
+ return generateCall(scope, decl, global, name, valueUnused);
85
91
 
86
92
  case 'NewExpression':
87
93
  return generateNew(scope, decl, global, name);
@@ -99,7 +105,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
99
105
  return generateUnary(scope, decl);
100
106
 
101
107
  case 'UpdateExpression':
102
- return generateUpdate(scope, decl);
108
+ return generateUpdate(scope, decl, global, name, valueUnused);
103
109
 
104
110
  case 'IfStatement':
105
111
  return generateIf(scope, decl);
@@ -155,7 +161,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
155
161
 
156
162
  case 'TaggedTemplateExpression': {
157
163
  const funcs = {
158
- asm: str => {
164
+ __Porffor_wasm: str => {
159
165
  let out = [];
160
166
 
161
167
  for (const line of str.split('\n')) {
@@ -163,8 +169,8 @@ const generate = (scope, decl, global = false, name = undefined) => {
163
169
  if (asm[0] === '') continue; // blank
164
170
 
165
171
  if (asm[0] === 'local') {
166
- const [ name, idx, type ] = asm.slice(1);
167
- scope.locals[name] = { idx: parseInt(idx), type: Valtype[type] };
172
+ const [ name, type ] = asm.slice(1);
173
+ scope.locals[name] = { idx: scope.localInd++, type: Valtype[type] };
168
174
  continue;
169
175
  }
170
176
 
@@ -174,7 +180,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
174
180
  }
175
181
 
176
182
  if (asm[0] === 'memory') {
177
- allocPage('asm instrinsic');
183
+ allocPage(scope, 'asm instrinsic');
178
184
  // todo: add to store/load offset insts
179
185
  continue;
180
186
  }
@@ -183,7 +189,11 @@ const generate = (scope, decl, global = false, name = undefined) => {
183
189
  if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
184
190
 
185
191
  if (!Array.isArray(inst)) inst = [ inst ];
186
- const immediates = asm.slice(1).map(x => parseInt(x));
192
+ const immediates = asm.slice(1).map(x => {
193
+ const int = parseInt(x);
194
+ if (Number.isNaN(int)) return scope.locals[x]?.idx;
195
+ return int;
196
+ });
187
197
 
188
198
  out.push([ ...inst, ...immediates ]);
189
199
  }
@@ -191,31 +201,40 @@ const generate = (scope, decl, global = false, name = undefined) => {
191
201
  return out;
192
202
  },
193
203
 
194
- __internal_print_type: str => {
195
- const type = getType(scope, str) - TYPES.number;
204
+ __Porffor_bs: str => [
205
+ ...makeString(scope, str, undefined, undefined, true),
196
206
 
197
- return [
198
- ...number(type),
199
- [ Opcodes.call, importedFuncs.print ],
207
+ ...number(TYPES._bytestring, Valtype.i32),
208
+ setLastType(scope)
209
+ ],
210
+ __Porffor_s: str => [
211
+ ...makeString(scope, str, undefined, undefined, false),
200
212
 
201
- // newline
202
- ...number(10),
203
- [ Opcodes.call, importedFuncs.printChar ]
204
- ];
205
- }
206
- }
213
+ ...number(TYPES.string, Valtype.i32),
214
+ setLastType(scope)
215
+ ],
216
+ };
207
217
 
208
218
  const name = decl.tag.name;
209
219
  // hack for inline asm
210
220
  if (!funcs[name]) return todo('tagged template expressions not implemented');
211
221
 
212
- const str = decl.quasi.quasis[0].value.raw;
222
+ const { quasis, expressions } = decl.quasi;
223
+ let str = quasis[0].value.raw;
224
+
225
+ for (let i = 0; i < expressions.length; i++) {
226
+ const e = expressions[i];
227
+ str += lookupName(scope, e.name)[0].idx;
228
+ str += quasis[i + 1].value.raw;
229
+ }
230
+
213
231
  return funcs[name](str);
214
232
  }
215
233
 
216
234
  default:
217
- if (decl.type.startsWith('TS')) {
218
- // ignore typescript nodes
235
+ // ignore typescript nodes
236
+ if (decl.type.startsWith('TS') ||
237
+ decl.type === 'ImportDeclaration' && decl.importKind === 'type') {
219
238
  return [];
220
239
  }
221
240
 
@@ -269,25 +288,28 @@ const generateIdent = (scope, decl) => {
269
288
  const name = mapName(rawName);
270
289
  let local = scope.locals[rawName];
271
290
 
272
- if (builtinVars[name]) {
291
+ if (Object.hasOwn(builtinVars, name)) {
273
292
  if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
274
- return builtinVars[name];
293
+
294
+ let wasm = builtinVars[name];
295
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name });
296
+ return wasm;
275
297
  }
276
298
 
277
- if (builtinFuncs[name] || internalConstrs[name]) {
299
+ if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
278
300
  // todo: return an actual something
279
301
  return number(1);
280
302
  }
281
303
 
282
- if (local === undefined) {
304
+ if (local?.idx === undefined) {
283
305
  // no local var with name
284
- if (importedFuncs.hasOwnProperty(name)) return number(importedFuncs[name]);
285
- if (funcIndex[name] !== undefined) return number(funcIndex[name]);
306
+ if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
307
+ if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name]);
286
308
 
287
- if (globals[name] !== undefined) return [ [ Opcodes.global_get, globals[name].idx ] ];
309
+ if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
288
310
  }
289
311
 
290
- if (local === undefined && rawName.startsWith('__')) {
312
+ if (local?.idx === undefined && rawName.startsWith('__')) {
291
313
  // return undefined if unknown key in already known var
292
314
  let parent = rawName.slice(2).split('_').slice(0, -1).join('_');
293
315
  if (parent.includes('_')) parent = '__' + parent;
@@ -296,7 +318,7 @@ const generateIdent = (scope, decl) => {
296
318
  if (!parentLookup[1]) return number(UNDEFINED);
297
319
  }
298
320
 
299
- if (local === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
321
+ if (local?.idx === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
300
322
 
301
323
  return [ [ Opcodes.local_get, local.idx ] ];
302
324
  };
@@ -404,9 +426,6 @@ const concatStrings = (scope, left, right, global, name, assign) => {
404
426
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
405
427
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
406
428
 
407
- const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
408
- if (aotWFA) addVarMeta(name, { wellFormed: undefined });
409
-
410
429
  if (assign) {
411
430
  const pointer = arrays.get(name ?? '$undeclared');
412
431
 
@@ -644,11 +663,12 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
644
663
  ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
645
664
  ];
646
665
 
647
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
666
+ const useTmp = knownType(scope, type) == null;
667
+ const tmp = useTmp && localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
648
668
 
649
669
  const def = [
650
670
  // if value != 0
651
- [ Opcodes.local_get, tmp ],
671
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
652
672
 
653
673
  // ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
654
674
  ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
@@ -660,7 +680,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
660
680
 
661
681
  return [
662
682
  ...wasm,
663
- [ Opcodes.local_set, tmp ],
683
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
664
684
 
665
685
  ...typeSwitch(scope, type, {
666
686
  // [TYPES.number]: def,
@@ -669,7 +689,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
669
689
  ...number(1, intOut ? Valtype.i32 : valtypeBinary)
670
690
  ],
671
691
  [TYPES.string]: [
672
- [ Opcodes.local_get, tmp ],
692
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
673
693
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
674
694
 
675
695
  // get length
@@ -680,16 +700,27 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
680
700
  [ Opcodes.i32_eqz ], */
681
701
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
682
702
  ],
703
+ [TYPES._bytestring]: [ // duplicate of string
704
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
705
+ ...(intIn ? [] : [ Opcodes.i32_to_u ]),
706
+
707
+ // get length
708
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
709
+
710
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
711
+ ],
683
712
  default: def
684
713
  }, intOut ? Valtype.i32 : valtypeBinary)
685
714
  ];
686
715
  };
687
716
 
688
717
  const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
689
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
718
+ const useTmp = knownType(scope, type) == null;
719
+ const tmp = useTmp && localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
720
+
690
721
  return [
691
722
  ...wasm,
692
- [ Opcodes.local_set, tmp ],
723
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
693
724
 
694
725
  ...typeSwitch(scope, type, {
695
726
  [TYPES._array]: [
@@ -697,7 +728,18 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
697
728
  ...number(0, intOut ? Valtype.i32 : valtypeBinary)
698
729
  ],
699
730
  [TYPES.string]: [
700
- [ Opcodes.local_get, tmp ],
731
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
732
+ ...(intIn ? [] : [ Opcodes.i32_to_u ]),
733
+
734
+ // get length
735
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
736
+
737
+ // if length == 0
738
+ [ Opcodes.i32_eqz ],
739
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
740
+ ],
741
+ [TYPES._bytestring]: [ // duplicate of string
742
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
701
743
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
702
744
 
703
745
  // get length
@@ -709,7 +751,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
709
751
  ],
710
752
  default: [
711
753
  // if value == 0
712
- [ Opcodes.local_get, tmp ],
754
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
713
755
 
714
756
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
715
757
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -719,10 +761,12 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
719
761
  };
720
762
 
721
763
  const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
722
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
764
+ const useTmp = knownType(scope, type) == null;
765
+ const tmp = useTmp && localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
766
+
723
767
  return [
724
768
  ...wasm,
725
- [ Opcodes.local_set, tmp ],
769
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
726
770
 
727
771
  ...typeSwitch(scope, type, {
728
772
  [TYPES.undefined]: [
@@ -731,7 +775,7 @@ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
731
775
  ],
732
776
  [TYPES.object]: [
733
777
  // object, null if == 0
734
- [ Opcodes.local_get, tmp ],
778
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
735
779
 
736
780
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
737
781
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -760,11 +804,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
760
804
  return performLogicOp(scope, op, left, right, leftType, rightType);
761
805
  }
762
806
 
807
+ const knownLeft = knownType(scope, leftType);
808
+ const knownRight = knownType(scope, rightType);
809
+
763
810
  const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
764
811
  const strictOp = op === '===' || op === '!==';
765
812
 
766
813
  const startOut = [], endOut = [];
767
- const finalise = out => startOut.concat(out, endOut);
814
+ const finalize = out => startOut.concat(out, endOut);
768
815
 
769
816
  // if strict (in)equal check types match
770
817
  if (strictOp) {
@@ -809,31 +856,32 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
809
856
  // todo: if equality op and an operand is undefined, return false
810
857
  // todo: niche null hell with 0
811
858
 
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
- // }
859
+ // todo: this should be dynamic but for now only static
860
+ if (knownLeft === TYPES.string || knownRight === TYPES.string) {
861
+ if (op === '+') {
862
+ // string concat (a + b)
863
+ return concatStrings(scope, left, right, _global, _name, assign);
864
+ }
865
+
866
+ // not an equality op, NaN
867
+ if (!eqOp) return number(NaN);
868
+
869
+ // else leave bool ops
870
+ // todo: convert string to number if string and number/bool
871
+ // todo: string (>|>=|<|<=) string
872
+
873
+ // string comparison
874
+ if (op === '===' || op === '==') {
875
+ return compareStrings(scope, left, right);
876
+ }
877
+
878
+ if (op === '!==' || op === '!=') {
879
+ return [
880
+ ...compareStrings(scope, left, right),
881
+ [ Opcodes.i32_eqz ]
882
+ ];
883
+ }
884
+ }
837
885
 
838
886
  let ops = operatorOpcode[valtype][op];
839
887
 
@@ -843,7 +891,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
843
891
  includeBuiltin(scope, builtinName);
844
892
  const idx = funcIndex[builtinName];
845
893
 
846
- return finalise([
894
+ return finalize([
847
895
  ...left,
848
896
  ...right,
849
897
  [ Opcodes.call, idx ]
@@ -858,9 +906,6 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
858
906
  let tmpLeft, tmpRight;
859
907
  // if equal op, check if strings for compareStrings
860
908
  if (op === '===' || op === '==' || op === '!==' || op === '!=') (() => {
861
- const knownLeft = knownType(scope, leftType);
862
- const knownRight = knownType(scope, rightType);
863
-
864
909
  // todo: intelligent partial skip later
865
910
  // if neither known are string, stop this madness
866
911
  if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
@@ -900,7 +945,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
900
945
  [ Opcodes.i32_or ],
901
946
  [ Opcodes.if, Blocktype.void ],
902
947
  ...number(0, Valtype.i32),
903
- [ Opcodes.br, 1 ],
948
+ [ Opcodes.br, 2 ],
904
949
  [ Opcodes.end ],
905
950
 
906
951
  ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
@@ -918,7 +963,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
918
963
  // }
919
964
  })();
920
965
 
921
- return finalise([
966
+ return finalize([
922
967
  ...left,
923
968
  ...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
924
969
  ...right,
@@ -935,7 +980,22 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
935
980
  return out;
936
981
  };
937
982
 
938
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [] }) => {
983
+ const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals = [], returns = [], localInd = 0 }) => {
984
+ return func({ name, params, locals, returns, localInd }, {
985
+ TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage,
986
+ builtin: name => {
987
+ let idx = funcIndex[name] ?? importedFuncs[name];
988
+ if (idx === undefined && builtinFuncs[name]) {
989
+ includeBuiltin(null, name);
990
+ idx = funcIndex[name];
991
+ }
992
+
993
+ return idx;
994
+ }
995
+ });
996
+ };
997
+
998
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [] }) => {
939
999
  const existing = funcs.find(x => x.name === name);
940
1000
  if (existing) return existing;
941
1001
 
@@ -947,6 +1007,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
947
1007
  locals[nameParam(i)] = { idx: i, type: allLocals[i] };
948
1008
  }
949
1009
 
1010
+ for (const x of _data) {
1011
+ const copy = { ...x };
1012
+ copy.offset += pages.size * pageSize;
1013
+ data.push(copy);
1014
+ }
1015
+
1016
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
1017
+
950
1018
  let baseGlobalIdx, i = 0;
951
1019
  for (const type of globalTypes) {
952
1020
  if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
@@ -1027,7 +1095,8 @@ const TYPES = {
1027
1095
 
1028
1096
  // these are not "typeof" types but tracked internally
1029
1097
  _array: 0x10,
1030
- _regexp: 0x11
1098
+ _regexp: 0x11,
1099
+ _bytestring: 0x12
1031
1100
  };
1032
1101
 
1033
1102
  const TYPE_NAMES = {
@@ -1041,13 +1110,17 @@ const TYPE_NAMES = {
1041
1110
  [TYPES.bigint]: 'BigInt',
1042
1111
 
1043
1112
  [TYPES._array]: 'Array',
1044
- [TYPES._regexp]: 'RegExp'
1113
+ [TYPES._regexp]: 'RegExp',
1114
+ [TYPES._bytestring]: 'ByteString'
1045
1115
  };
1046
1116
 
1047
1117
  const getType = (scope, _name) => {
1048
1118
  const name = mapName(_name);
1049
1119
 
1120
+ if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
1050
1121
  if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
1122
+
1123
+ if (typedInput && globals[name]?.metadata?.type != null) return number(globals[name].metadata.type, Valtype.i32);
1051
1124
  if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
1052
1125
 
1053
1126
  let type = TYPES.undefined;
@@ -1094,6 +1167,8 @@ const getNodeType = (scope, node) => {
1094
1167
  if (node.type === 'Literal') {
1095
1168
  if (node.regex) return TYPES._regexp;
1096
1169
 
1170
+ if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES._bytestring;
1171
+
1097
1172
  return TYPES[typeof node.value];
1098
1173
  }
1099
1174
 
@@ -1107,6 +1182,15 @@ const getNodeType = (scope, node) => {
1107
1182
 
1108
1183
  if (node.type === 'CallExpression' || node.type === 'NewExpression') {
1109
1184
  const name = node.callee.name;
1185
+ if (!name) {
1186
+ // iife
1187
+ if (scope.locals['#last_type']) return [ getLastType(scope) ];
1188
+
1189
+ // presume
1190
+ // todo: warn here?
1191
+ return TYPES.number;
1192
+ }
1193
+
1110
1194
  const func = funcs.find(x => x.name === name);
1111
1195
 
1112
1196
  if (func) {
@@ -1114,7 +1198,7 @@ const getNodeType = (scope, node) => {
1114
1198
  if (func.returnType) return func.returnType;
1115
1199
  }
1116
1200
 
1117
- if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1201
+ if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1118
1202
  if (internalConstrs[name]) return internalConstrs[name].type;
1119
1203
 
1120
1204
  // check if this is a prototype function
@@ -1125,10 +1209,15 @@ const getNodeType = (scope, node) => {
1125
1209
  const spl = name.slice(2).split('_');
1126
1210
 
1127
1211
  const func = spl[spl.length - 1];
1128
- const protoFuncs = Object.values(prototypeFuncs).filter(x => x[func] != null);
1212
+ const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES._bytestring && prototypeFuncs[x][func] != null);
1129
1213
  if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1130
1214
  }
1131
1215
 
1216
+ if (name.startsWith('__Porffor_wasm_')) {
1217
+ // todo: return undefined for non-returning ops
1218
+ return TYPES.number;
1219
+ }
1220
+
1132
1221
  if (scope.locals['#last_type']) return [ getLastType(scope) ];
1133
1222
 
1134
1223
  // presume
@@ -1177,6 +1266,14 @@ const getNodeType = (scope, node) => {
1177
1266
 
1178
1267
  if (node.type === 'BinaryExpression') {
1179
1268
  if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
1269
+ if (node.operator !== '+') return TYPES.number;
1270
+
1271
+ const knownLeft = knownType(scope, getNodeType(scope, node.left));
1272
+ const knownRight = knownType(scope, getNodeType(scope, node.right));
1273
+
1274
+ // todo: this should be dynamic but for now only static
1275
+ if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
1276
+
1180
1277
  return TYPES.number;
1181
1278
 
1182
1279
  // todo: string concat types
@@ -1201,7 +1298,7 @@ const getNodeType = (scope, node) => {
1201
1298
  if (node.operator === '!') return TYPES.boolean;
1202
1299
  if (node.operator === 'void') return TYPES.undefined;
1203
1300
  if (node.operator === 'delete') return TYPES.boolean;
1204
- if (node.operator === 'typeof') return TYPES.string;
1301
+ if (node.operator === 'typeof') return Prefs.bytestring ? TYPES._bytestring : TYPES.string;
1205
1302
 
1206
1303
  return TYPES.number;
1207
1304
  }
@@ -1210,7 +1307,13 @@ const getNodeType = (scope, node) => {
1210
1307
  // hack: if something.length, number type
1211
1308
  if (node.property.name === 'length') return TYPES.number;
1212
1309
 
1213
- // we cannot guess
1310
+ // ts hack
1311
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
1312
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
1313
+
1314
+ if (scope.locals['#last_type']) return [ getLastType(scope) ];
1315
+
1316
+ // presume
1214
1317
  return TYPES.number;
1215
1318
  }
1216
1319
 
@@ -1230,8 +1333,8 @@ const getNodeType = (scope, node) => {
1230
1333
  const generateLiteral = (scope, decl, global, name) => {
1231
1334
  if (decl.value === null) return number(NULL);
1232
1335
 
1336
+ // hack: just return 1 for regex literals
1233
1337
  if (decl.regex) {
1234
- scope.regex[name] = decl.regex;
1235
1338
  return number(1);
1236
1339
  }
1237
1340
 
@@ -1244,16 +1347,7 @@ const generateLiteral = (scope, decl, global, name) => {
1244
1347
  return number(decl.value ? 1 : 0);
1245
1348
 
1246
1349
  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];
1350
+ return makeString(scope, decl.value, global, name);
1257
1351
 
1258
1352
  default:
1259
1353
  return todo(`cannot generate literal of type ${typeof decl.value}`);
@@ -1273,10 +1367,10 @@ const countLeftover = wasm => {
1273
1367
  if (inst[0] === Opcodes.end) depth--;
1274
1368
 
1275
1369
  if (depth === 0)
1276
- 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)) {}
1370
+ if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1371
+ 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
1372
  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;
1373
+ else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
1280
1374
  else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1281
1375
  else if (inst[0] === Opcodes.return) count = 0;
1282
1376
  else if (inst[0] === Opcodes.call) {
@@ -1302,7 +1396,7 @@ const disposeLeftover = wasm => {
1302
1396
  const generateExp = (scope, decl) => {
1303
1397
  const expression = decl.expression;
1304
1398
 
1305
- const out = generate(scope, expression);
1399
+ const out = generate(scope, expression, undefined, undefined, true);
1306
1400
  disposeLeftover(out);
1307
1401
 
1308
1402
  return out;
@@ -1360,7 +1454,7 @@ const RTArrayUtil = {
1360
1454
  ]
1361
1455
  };
1362
1456
 
1363
- const generateCall = (scope, decl, _global, _name) => {
1457
+ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1364
1458
  /* const callee = decl.callee;
1365
1459
  const args = decl.arguments;
1366
1460
 
@@ -1426,8 +1520,8 @@ const generateCall = (scope, decl, _global, _name) => {
1426
1520
  // literal.func()
1427
1521
  if (!name && decl.callee.type === 'MemberExpression') {
1428
1522
  // megahack for /regex/.func()
1429
- if (decl.callee.object.regex) {
1430
- const funcName = decl.callee.property.name;
1523
+ const funcName = decl.callee.property.name;
1524
+ if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
1431
1525
  const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1432
1526
 
1433
1527
  funcIndex[func.name] = func.index;
@@ -1469,8 +1563,7 @@ const generateCall = (scope, decl, _global, _name) => {
1469
1563
 
1470
1564
  if (protoName) {
1471
1565
  const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1472
- const f = prototypeFuncs[x][protoName];
1473
- if (f) acc[x] = f;
1566
+ if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
1474
1567
  return acc;
1475
1568
  }, {});
1476
1569
 
@@ -1479,10 +1572,18 @@ const generateCall = (scope, decl, _global, _name) => {
1479
1572
  // use local for cached i32 length as commonly used
1480
1573
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1481
1574
  const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
1482
- const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
1483
1575
 
1484
1576
  // TODO: long-term, prototypes should be their individual separate funcs
1485
1577
 
1578
+ const rawPointer = [
1579
+ ...generate(scope, target),
1580
+ Opcodes.i32_to_u
1581
+ ];
1582
+
1583
+ const usePointerCache = !Object.values(protoCands).every(x => x.noPointerCache === true);
1584
+ const getPointer = usePointerCache ? [ [ Opcodes.local_get, pointerLocal ] ] : rawPointer;
1585
+
1586
+ let allOptUnused = true;
1486
1587
  let lengthI32CacheUsed = false;
1487
1588
  const protoBC = {};
1488
1589
  for (const x in protoCands) {
@@ -1502,6 +1603,7 @@ const generateCall = (scope, decl, _global, _name) => {
1502
1603
  const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
1503
1604
  const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
1504
1605
 
1606
+ let optUnused = false;
1505
1607
  const protoOut = protoFunc(getPointer, {
1506
1608
  getCachedI32: () => {
1507
1609
  lengthI32CacheUsed = true;
@@ -1516,10 +1618,15 @@ const generateCall = (scope, decl, _global, _name) => {
1516
1618
  return makeArray(scope, {
1517
1619
  rawElements: new Array(length)
1518
1620
  }, _global, _name, true, itemType);
1621
+ }, () => {
1622
+ optUnused = true;
1623
+ return unusedValue;
1519
1624
  });
1520
1625
 
1626
+ if (!optUnused) allOptUnused = false;
1627
+
1521
1628
  protoBC[x] = [
1522
- [ Opcodes.block, valtypeBinary ],
1629
+ [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1523
1630
  ...protoOut,
1524
1631
 
1525
1632
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
@@ -1528,11 +1635,13 @@ const generateCall = (scope, decl, _global, _name) => {
1528
1635
  ];
1529
1636
  }
1530
1637
 
1531
- return [
1532
- ...generate(scope, target),
1638
+ // todo: if some cands use optUnused and some don't, we will probably crash
1533
1639
 
1534
- Opcodes.i32_to_u,
1535
- [ Opcodes.local_set, pointerLocal ],
1640
+ return [
1641
+ ...(usePointerCache ? [
1642
+ ...rawPointer,
1643
+ [ Opcodes.local_set, pointerLocal ],
1644
+ ] : []),
1536
1645
 
1537
1646
  ...(!lengthI32CacheUsed ? [] : [
1538
1647
  ...RTArrayUtil.getLengthI32(getPointer),
@@ -1544,7 +1653,7 @@ const generateCall = (scope, decl, _global, _name) => {
1544
1653
 
1545
1654
  // TODO: error better
1546
1655
  default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1547
- }, valtypeBinary),
1656
+ }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1548
1657
  ];
1549
1658
  }
1550
1659
  }
@@ -1583,6 +1692,32 @@ const generateCall = (scope, decl, _global, _name) => {
1583
1692
  idx = -1;
1584
1693
  }
1585
1694
 
1695
+ if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1696
+ const wasmOps = {
1697
+ // pointer, align, offset
1698
+ i32_load8_u: { imms: 2, args: 1 },
1699
+ // pointer, value, align, offset
1700
+ i32_store8: { imms: 2, args: 2 },
1701
+ };
1702
+
1703
+ const opName = name.slice('__Porffor_wasm_'.length);
1704
+
1705
+ if (wasmOps[opName]) {
1706
+ const op = wasmOps[opName];
1707
+
1708
+ const argOut = [];
1709
+ for (let i = 0; i < op.args; i++) argOut.push(...generate(scope, decl.arguments[i]));
1710
+
1711
+ // literals only
1712
+ const imms = decl.arguments.slice(op.args).map(x => x.value);
1713
+
1714
+ return [
1715
+ ...argOut,
1716
+ [ Opcodes[opName], ...imms ]
1717
+ ];
1718
+ }
1719
+ }
1720
+
1586
1721
  if (idx === undefined) {
1587
1722
  if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`);
1588
1723
  return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
@@ -1591,7 +1726,9 @@ const generateCall = (scope, decl, _global, _name) => {
1591
1726
  const func = funcs.find(x => x.index === idx);
1592
1727
 
1593
1728
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1594
- const paramCount = func && (userFunc ? func.params.length / 2 : func.params.length);
1729
+ const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1730
+ const typedReturns = userFunc || builtinFuncs[name]?.typedReturns;
1731
+ const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1595
1732
 
1596
1733
  let args = decl.arguments;
1597
1734
  if (func && args.length < paramCount) {
@@ -1607,14 +1744,20 @@ const generateCall = (scope, decl, _global, _name) => {
1607
1744
  if (func && func.throws) scope.throws = true;
1608
1745
 
1609
1746
  let out = [];
1610
- for (const arg of args) {
1747
+ for (let i = 0; i < args.length; i++) {
1748
+ const arg = args[i];
1611
1749
  out = out.concat(generate(scope, arg));
1612
- if (userFunc) out = out.concat(getNodeType(scope, arg));
1750
+
1751
+ if (builtinFuncs[name] && builtinFuncs[name].params[i] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1752
+ out.push(Opcodes.i32_to);
1753
+ }
1754
+
1755
+ if (typedParams) out = out.concat(getNodeType(scope, arg));
1613
1756
  }
1614
1757
 
1615
1758
  out.push([ Opcodes.call, idx ]);
1616
1759
 
1617
- if (!userFunc) {
1760
+ if (!typedReturns) {
1618
1761
  // let type;
1619
1762
  // if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
1620
1763
  // if (internalConstrs[name]) type = internalConstrs[name].type;
@@ -1626,6 +1769,10 @@ const generateCall = (scope, decl, _global, _name) => {
1626
1769
  // );
1627
1770
  } else out.push(setLastType(scope));
1628
1771
 
1772
+ if (builtinFuncs[name] && builtinFuncs[name].returns[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1773
+ out.push(Opcodes.i32_from);
1774
+ }
1775
+
1629
1776
  return out;
1630
1777
  };
1631
1778
 
@@ -1665,14 +1812,102 @@ const knownType = (scope, type) => {
1665
1812
  return null;
1666
1813
  };
1667
1814
 
1815
+ const brTable = (input, bc, returns) => {
1816
+ const out = [];
1817
+ const keys = Object.keys(bc);
1818
+ const count = keys.length;
1819
+
1820
+ if (count === 1) {
1821
+ // return [
1822
+ // ...input,
1823
+ // ...bc[keys[0]]
1824
+ // ];
1825
+ return bc[keys[0]];
1826
+ }
1827
+
1828
+ if (count === 2) {
1829
+ // just use if else
1830
+ const other = keys.find(x => x !== 'default');
1831
+ return [
1832
+ ...input,
1833
+ ...number(other, Valtype.i32),
1834
+ [ Opcodes.i32_eq ],
1835
+ [ Opcodes.if, returns ],
1836
+ ...bc[other],
1837
+ [ Opcodes.else ],
1838
+ ...bc.default,
1839
+ [ Opcodes.end ]
1840
+ ];
1841
+ }
1842
+
1843
+ for (let i = 0; i < count; i++) {
1844
+ if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
1845
+ else out.push([ Opcodes.block, Blocktype.void ]);
1846
+ }
1847
+
1848
+ const nums = keys.filter(x => +x);
1849
+ const offset = Math.min(...nums);
1850
+ const max = Math.max(...nums);
1851
+
1852
+ const table = [];
1853
+ let br = 1;
1854
+
1855
+ for (let i = offset; i <= max; i++) {
1856
+ // if branch for this num, go to that block
1857
+ if (bc[i]) {
1858
+ table.push(br);
1859
+ br++;
1860
+ continue;
1861
+ }
1862
+
1863
+ // else default
1864
+ table.push(0);
1865
+ }
1866
+
1867
+ out.push(
1868
+ [ Opcodes.block, Blocktype.void ],
1869
+ ...input,
1870
+ ...(offset > 0 ? [
1871
+ ...number(offset, Valtype.i32),
1872
+ [ Opcodes.i32_sub ]
1873
+ ] : []),
1874
+ [ Opcodes.br_table, ...encodeVector(table), 0 ]
1875
+ );
1876
+
1877
+ // if you can guess why we sort the wrong way and then reverse
1878
+ // (instead of just sorting the correct way)
1879
+ // dm me and if you are correct and the first person
1880
+ // I will somehow shout you out or something
1881
+ const orderedBc = keys.sort((a, b) => b - a).reverse();
1882
+
1883
+ br = count - 1;
1884
+ for (const x of orderedBc) {
1885
+ out.push(
1886
+ [ Opcodes.end ],
1887
+ ...bc[x],
1888
+ ...(br === 0 ? [] : [ [ Opcodes.br, br ] ])
1889
+ );
1890
+ br--;
1891
+ }
1892
+
1893
+ return [
1894
+ ...out,
1895
+ [ Opcodes.end, 'br table end' ]
1896
+ ];
1897
+ };
1898
+
1668
1899
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1900
+ if (!Prefs.bytestring) delete bc[TYPES._bytestring];
1901
+
1669
1902
  const known = knownType(scope, type);
1670
1903
  if (known != null) {
1671
1904
  return bc[known] ?? bc.default;
1672
1905
  }
1673
1906
 
1674
- const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
1907
+ if (Prefs.typeswitchUseBrtable)
1908
+ return brTable(type, bc, returns);
1675
1909
 
1910
+ const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
1676
1911
  const out = [
1677
1912
  ...type,
1678
1913
  [ Opcodes.local_set, tmp ],
@@ -1704,7 +1939,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1704
1939
  return out;
1705
1940
  };
1706
1941
 
1707
- const allocVar = (scope, name, global = false) => {
1942
+ const allocVar = (scope, name, global = false, type = true) => {
1708
1943
  const target = global ? globals : scope.locals;
1709
1944
 
1710
1945
  // already declared
@@ -1718,8 +1953,10 @@ const allocVar = (scope, name, global = false) => {
1718
1953
  let idx = global ? globalInd++ : scope.localInd++;
1719
1954
  target[name] = { idx, type: valtypeBinary };
1720
1955
 
1721
- let typeIdx = global ? globalInd++ : scope.localInd++;
1722
- target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
1956
+ if (type) {
1957
+ let typeIdx = global ? globalInd++ : scope.localInd++;
1958
+ target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
1959
+ }
1723
1960
 
1724
1961
  return idx;
1725
1962
  };
@@ -1739,6 +1976,7 @@ const typeAnnoToPorfType = x => {
1739
1976
 
1740
1977
  switch (x) {
1741
1978
  case 'i32':
1979
+ case 'i64':
1742
1980
  return TYPES.number;
1743
1981
  }
1744
1982
 
@@ -1762,6 +2000,8 @@ const extractTypeAnnotation = decl => {
1762
2000
  const typeName = type;
1763
2001
  type = typeAnnoToPorfType(type);
1764
2002
 
2003
+ if (type === TYPES._bytestring && !Prefs.bytestring) type = TYPES.string;
2004
+
1765
2005
  // if (decl.name) console.log(decl.name, { type, elementType });
1766
2006
 
1767
2007
  return { type, typeName, elementType };
@@ -1778,6 +2018,8 @@ const generateVar = (scope, decl) => {
1778
2018
  for (const x of decl.declarations) {
1779
2019
  const name = mapName(x.id.name);
1780
2020
 
2021
+ if (!name) return todo('destructuring is not supported yet');
2022
+
1781
2023
  if (x.init && isFuncType(x.init.type)) {
1782
2024
  // hack for let a = function () { ... }
1783
2025
  x.init.id = { name };
@@ -1793,7 +2035,12 @@ const generateVar = (scope, decl) => {
1793
2035
  continue; // always ignore
1794
2036
  }
1795
2037
 
1796
- let idx = allocVar(scope, name, global);
2038
+ let idx = allocVar(scope, name, global, !x.id.typeAnnotation);
2039
+
2040
+ if (typedInput && x.id.typeAnnotation) {
2041
+ addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
2042
+ }
2043
+
1797
2044
  if (x.init) {
1798
2045
  out = out.concat(generate(scope, x.init, global, name));
1799
2046
 
@@ -1803,16 +2050,13 @@ const generateVar = (scope, decl) => {
1803
2050
 
1804
2051
  // hack: this follows spec properly but is mostly unneeded 😅
1805
2052
  // out.push(...setType(scope, name, x.init ? getNodeType(scope, x.init) : TYPES.undefined));
1806
-
1807
- if (typedInput && x.id.typeAnnotation) {
1808
- addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
1809
- }
1810
2053
  }
1811
2054
 
1812
2055
  return out;
1813
2056
  };
1814
2057
 
1815
- const generateAssign = (scope, decl) => {
2058
+ // todo: optimize this func for valueUnused
2059
+ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
1816
2060
  const { type, name } = decl.left;
1817
2061
 
1818
2062
  if (type === 'ObjectPattern') {
@@ -1915,6 +2159,8 @@ const generateAssign = (scope, decl) => {
1915
2159
  ];
1916
2160
  }
1917
2161
 
2162
+ if (!name) return todo('destructuring is not supported yet');
2163
+
1918
2164
  const [ local, isGlobal ] = lookupName(scope, name);
1919
2165
 
1920
2166
  if (local === undefined) {
@@ -1961,9 +2207,7 @@ const generateAssign = (scope, decl) => {
1961
2207
  ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
1962
2208
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1963
2209
 
1964
- getLastType(scope),
1965
- // hack: type is idx+1
1966
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2210
+ ...setType(scope, name, getLastType(scope))
1967
2211
  ];
1968
2212
  }
1969
2213
 
@@ -1974,9 +2218,7 @@ const generateAssign = (scope, decl) => {
1974
2218
 
1975
2219
  // todo: string concat types
1976
2220
 
1977
- // hack: type is idx+1
1978
- ...number(TYPES.number, Valtype.i32),
1979
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2221
+ ...setType(scope, name, TYPES.number)
1980
2222
  ];
1981
2223
  };
1982
2224
 
@@ -2053,6 +2295,8 @@ const generateUnary = (scope, decl) => {
2053
2295
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
2054
2296
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
2055
2297
 
2298
+ [TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2299
+
2056
2300
  // object and internal types
2057
2301
  default: makeString(scope, 'object', false, '#typeof_result'),
2058
2302
  });
@@ -2062,7 +2306,7 @@ const generateUnary = (scope, decl) => {
2062
2306
  }
2063
2307
  };
2064
2308
 
2065
- const generateUpdate = (scope, decl) => {
2309
+ const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
2066
2310
  const { name } = decl.argument;
2067
2311
 
2068
2312
  const [ local, isGlobal ] = lookupName(scope, name);
@@ -2075,7 +2319,7 @@ const generateUpdate = (scope, decl) => {
2075
2319
  const out = [];
2076
2320
 
2077
2321
  out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2078
- if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2322
+ if (!decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2079
2323
 
2080
2324
  switch (decl.operator) {
2081
2325
  case '++':
@@ -2088,7 +2332,7 @@ const generateUpdate = (scope, decl) => {
2088
2332
  }
2089
2333
 
2090
2334
  out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
2091
- if (decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2335
+ if (decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2092
2336
 
2093
2337
  return out;
2094
2338
  };
@@ -2151,15 +2395,17 @@ const generateFor = (scope, decl) => {
2151
2395
  const out = [];
2152
2396
 
2153
2397
  if (decl.init) {
2154
- out.push(...generate(scope, decl.init));
2398
+ out.push(...generate(scope, decl.init, false, undefined, true));
2155
2399
  disposeLeftover(out);
2156
2400
  }
2157
2401
 
2158
2402
  out.push([ Opcodes.loop, Blocktype.void ]);
2159
2403
  depth.push('for');
2160
2404
 
2161
- out.push(...generate(scope, decl.test));
2162
- out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
2405
+ if (decl.test) out.push(...generate(scope, decl.test), Opcodes.i32_to);
2406
+ else out.push(...number(1, Valtype.i32));
2407
+
2408
+ out.push([ Opcodes.if, Blocktype.void ]);
2163
2409
  depth.push('if');
2164
2410
 
2165
2411
  out.push([ Opcodes.block, Blocktype.void ]);
@@ -2167,8 +2413,7 @@ const generateFor = (scope, decl) => {
2167
2413
  out.push(...generate(scope, decl.body));
2168
2414
  out.push([ Opcodes.end ]);
2169
2415
 
2170
- out.push(...generate(scope, decl.update));
2171
- depth.pop();
2416
+ if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
2172
2417
 
2173
2418
  out.push([ Opcodes.br, 1 ]);
2174
2419
  out.push([ Opcodes.end ], [ Opcodes.end ]);
@@ -2225,7 +2470,13 @@ const generateForOf = (scope, decl) => {
2225
2470
  // setup local for left
2226
2471
  generate(scope, decl.left);
2227
2472
 
2228
- const leftName = decl.left.declarations[0].id.name;
2473
+ let leftName = decl.left.declarations?.[0]?.id?.name;
2474
+ if (!leftName && decl.left.name) {
2475
+ leftName = decl.left.name;
2476
+
2477
+ generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
2478
+ }
2479
+
2229
2480
  const [ local, isGlobal ] = lookupName(scope, leftName);
2230
2481
 
2231
2482
  depth.push('block');
@@ -2234,13 +2485,14 @@ const generateForOf = (scope, decl) => {
2234
2485
  // // todo: we should only do this for strings but we don't know at compile-time :(
2235
2486
  // hack: this is naughty and will break things!
2236
2487
  let newOut = number(0, Valtype.f64), newPointer = -1;
2237
- if (pages.hasString) {
2488
+ if (pages.hasAnyString) {
2238
2489
  0, [ newOut, newPointer ] = makeArray(scope, {
2239
2490
  rawElements: new Array(1)
2240
2491
  }, isGlobal, leftName, true, 'i16');
2241
2492
  }
2242
2493
 
2243
2494
  // set type for local
2495
+ // todo: optimize away counter and use end pointer
2244
2496
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2245
2497
  [TYPES._array]: [
2246
2498
  ...setType(scope, leftName, TYPES.number),
@@ -2365,7 +2617,7 @@ const generateThrow = (scope, decl) => {
2365
2617
  // hack: throw new X("...") -> throw "..."
2366
2618
  if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
2367
2619
  constructor = decl.argument.callee.name;
2368
- message = decl.argument.arguments[0].value;
2620
+ message = decl.argument.arguments[0]?.value ?? '';
2369
2621
  }
2370
2622
 
2371
2623
  if (tags.length === 0) tags.push({
@@ -2377,6 +2629,9 @@ const generateThrow = (scope, decl) => {
2377
2629
  let exceptId = exceptions.push({ constructor, message }) - 1;
2378
2630
  let tagIdx = tags[0].idx;
2379
2631
 
2632
+ scope.exceptions ??= [];
2633
+ scope.exceptions.push(exceptId);
2634
+
2380
2635
  // todo: write a description of how this works lol
2381
2636
 
2382
2637
  return [
@@ -2421,25 +2676,31 @@ const generateAssignPat = (scope, decl) => {
2421
2676
  };
2422
2677
 
2423
2678
  let pages = new Map();
2424
- const allocPage = (reason, type) => {
2679
+ const allocPage = (scope, reason, type) => {
2425
2680
  if (pages.has(reason)) return pages.get(reason).ind;
2426
2681
 
2427
2682
  if (reason.startsWith('array:')) pages.hasArray = true;
2428
2683
  if (reason.startsWith('string:')) pages.hasString = true;
2684
+ if (reason.startsWith('bytestring:')) pages.hasByteString = true;
2685
+ if (reason.includes('string:')) pages.hasAnyString = true;
2429
2686
 
2430
2687
  const ind = pages.size;
2431
2688
  pages.set(reason, { ind, type });
2432
2689
 
2433
- if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
2690
+ scope.pages ??= new Map();
2691
+ scope.pages.set(reason, { ind, type });
2692
+
2693
+ if (Prefs.allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
2434
2694
 
2435
2695
  return ind;
2436
2696
  };
2437
2697
 
2698
+ // todo: add scope.pages
2438
2699
  const freePage = reason => {
2439
2700
  const { ind } = pages.get(reason);
2440
2701
  pages.delete(reason);
2441
2702
 
2442
- if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
2703
+ if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
2443
2704
 
2444
2705
  return ind;
2445
2706
  };
@@ -2459,25 +2720,34 @@ const StoreOps = {
2459
2720
  f64: Opcodes.f64_store,
2460
2721
 
2461
2722
  // expects i32 input!
2462
- i16: Opcodes.i32_store16
2723
+ i8: Opcodes.i32_store8,
2724
+ i16: Opcodes.i32_store16,
2463
2725
  };
2464
2726
 
2465
2727
  let data = [];
2466
2728
 
2467
- const compileBytes = (val, itemType, signed = true) => {
2729
+ const compileBytes = (val, itemType) => {
2468
2730
  // todo: this is a mess and needs confirming / ????
2469
2731
  switch (itemType) {
2470
2732
  case 'i8': return [ val % 256 ];
2471
- case 'i16': return [ val % 256, Math.floor(val / 256) ];
2472
-
2473
- case 'i32':
2474
- case 'i64':
2475
- return enforceFourBytes(signedLEB128(val));
2733
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
2734
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
2735
+ case 'i32': return [...new Uint8Array(new Int32Array([ val ]).buffer)];
2736
+ // todo: i64
2476
2737
 
2477
2738
  case 'f64': return ieee754_binary64(val);
2478
2739
  }
2479
2740
  };
2480
2741
 
2742
+ const getAllocType = itemType => {
2743
+ switch (itemType) {
2744
+ case 'i8': return 'bytestring';
2745
+ case 'i16': return 'string';
2746
+
2747
+ default: return 'array';
2748
+ }
2749
+ };
2750
+
2481
2751
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2482
2752
  const out = [];
2483
2753
 
@@ -2487,7 +2757,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2487
2757
 
2488
2758
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2489
2759
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2490
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
2760
+ arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2491
2761
  }
2492
2762
 
2493
2763
  const pointer = arrays.get(name);
@@ -2498,19 +2768,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2498
2768
  const valtype = itemTypeToValtype[itemType];
2499
2769
  const length = elements.length;
2500
2770
 
2501
- if (firstAssign && useRawElements) {
2502
- let bytes = compileBytes(length, 'i32');
2771
+ if (firstAssign && useRawElements && !Prefs.noData) {
2772
+ // if length is 0 memory/data will just be 0000... anyway
2773
+ if (length !== 0) {
2774
+ let bytes = compileBytes(length, 'i32');
2503
2775
 
2504
- if (!initEmpty) for (let i = 0; i < length; i++) {
2505
- if (elements[i] == null) continue;
2776
+ if (!initEmpty) for (let i = 0; i < length; i++) {
2777
+ if (elements[i] == null) continue;
2506
2778
 
2507
- bytes.push(...compileBytes(elements[i], itemType));
2508
- }
2779
+ bytes.push(...compileBytes(elements[i], itemType));
2780
+ }
2509
2781
 
2510
- data.push({
2511
- offset: pointer,
2512
- bytes
2513
- });
2782
+ const ind = data.push({
2783
+ offset: pointer,
2784
+ bytes
2785
+ }) - 1;
2786
+
2787
+ scope.data ??= [];
2788
+ scope.data.push(ind);
2789
+ }
2514
2790
 
2515
2791
  // local value as pointer
2516
2792
  out.push(...number(pointer));
@@ -2533,7 +2809,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2533
2809
  out.push(
2534
2810
  ...number(0, Valtype.i32),
2535
2811
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
2536
- [ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2812
+ [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2537
2813
  );
2538
2814
  }
2539
2815
 
@@ -2543,15 +2819,31 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2543
2819
  return [ out, pointer ];
2544
2820
  };
2545
2821
 
2546
- const makeString = (scope, str, global = false, name = '$undeclared') => {
2822
+ const byteStringable = str => {
2823
+ if (!Prefs.bytestring) return false;
2824
+
2825
+ for (let i = 0; i < str.length; i++) {
2826
+ if (str.charCodeAt(i) > 0xFF) return false;
2827
+ }
2828
+
2829
+ return true;
2830
+ };
2831
+
2832
+ const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
2547
2833
  const rawElements = new Array(str.length);
2834
+ let byteStringable = Prefs.bytestring;
2548
2835
  for (let i = 0; i < str.length; i++) {
2549
- rawElements[i] = str.charCodeAt(i);
2836
+ const c = str.charCodeAt(i);
2837
+ rawElements[i] = c;
2838
+
2839
+ if (byteStringable && c > 0xFF) byteStringable = false;
2550
2840
  }
2551
2841
 
2842
+ if (byteStringable && forceBytestring === false) byteStringable = false;
2843
+
2552
2844
  return makeArray(scope, {
2553
2845
  rawElements
2554
- }, global, name, false, 'i16')[0];
2846
+ }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2555
2847
  };
2556
2848
 
2557
2849
  let arrays = new Map();
@@ -2579,10 +2871,13 @@ export const generateMember = (scope, decl, _global, _name) => {
2579
2871
  ];
2580
2872
  }
2581
2873
 
2874
+ const object = generate(scope, decl.object);
2875
+ const property = generate(scope, decl.property);
2876
+
2582
2877
  // // todo: we should only do this for strings but we don't know at compile-time :(
2583
2878
  // hack: this is naughty and will break things!
2584
- let newOut = number(0, Valtype.f64), newPointer = -1;
2585
- if (pages.hasString) {
2879
+ let newOut = number(0, valtypeBinary), newPointer = -1;
2880
+ if (pages.hasAnyString) {
2586
2881
  0, [ newOut, newPointer ] = makeArray(scope, {
2587
2882
  rawElements: new Array(1)
2588
2883
  }, _global, _name, true, 'i16');
@@ -2591,7 +2886,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2591
2886
  return typeSwitch(scope, getNodeType(scope, decl.object), {
2592
2887
  [TYPES._array]: [
2593
2888
  // get index as valtype
2594
- ...generate(scope, decl.property),
2889
+ ...property,
2595
2890
 
2596
2891
  // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2597
2892
  Opcodes.i32_to_u,
@@ -2599,7 +2894,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2599
2894
  [ Opcodes.i32_mul ],
2600
2895
 
2601
2896
  ...(aotPointer ? [] : [
2602
- ...generate(scope, decl.object),
2897
+ ...object,
2603
2898
  Opcodes.i32_to_u,
2604
2899
  [ Opcodes.i32_add ]
2605
2900
  ]),
@@ -2618,14 +2913,14 @@ export const generateMember = (scope, decl, _global, _name) => {
2618
2913
 
2619
2914
  ...number(0, Valtype.i32), // base 0 for store later
2620
2915
 
2621
- ...generate(scope, decl.property),
2622
-
2916
+ ...property,
2623
2917
  Opcodes.i32_to_u,
2918
+
2624
2919
  ...number(ValtypeSize.i16, Valtype.i32),
2625
2920
  [ Opcodes.i32_mul ],
2626
2921
 
2627
2922
  ...(aotPointer ? [] : [
2628
- ...generate(scope, decl.object),
2923
+ ...object,
2629
2924
  Opcodes.i32_to_u,
2630
2925
  [ Opcodes.i32_add ]
2631
2926
  ]),
@@ -2642,8 +2937,36 @@ export const generateMember = (scope, decl, _global, _name) => {
2642
2937
  ...number(TYPES.string, Valtype.i32),
2643
2938
  setLastType(scope)
2644
2939
  ],
2940
+ [TYPES._bytestring]: [
2941
+ // setup new/out array
2942
+ ...newOut,
2943
+ [ Opcodes.drop ],
2944
+
2945
+ ...number(0, Valtype.i32), // base 0 for store later
2946
+
2947
+ ...property,
2948
+ Opcodes.i32_to_u,
2949
+
2950
+ ...(aotPointer ? [] : [
2951
+ ...object,
2952
+ Opcodes.i32_to_u,
2953
+ [ Opcodes.i32_add ]
2954
+ ]),
2955
+
2956
+ // load current string ind {arg}
2957
+ [ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2958
+
2959
+ // store to new string ind 0
2960
+ [ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2961
+
2962
+ // return new string (page)
2963
+ ...number(newPointer),
2964
+
2965
+ ...number(TYPES._bytestring, Valtype.i32),
2966
+ setLastType(scope)
2967
+ ],
2645
2968
 
2646
- default: [ [ Opcodes.unreachable ] ]
2969
+ default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
2647
2970
  });
2648
2971
  };
2649
2972
 
@@ -2660,13 +2983,16 @@ const objectHack = node => {
2660
2983
  // if object is not identifier or another member exp, give up
2661
2984
  if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
2662
2985
 
2663
- if (!objectName) objectName = objectHack(node.object).name.slice(2);
2986
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2664
2987
 
2665
2988
  // if .length, give up (hack within a hack!)
2666
2989
  if (node.property.name === 'length') return node;
2667
2990
 
2991
+ // no object name, give up
2992
+ if (!objectName) return node;
2993
+
2668
2994
  const name = '__' + objectName + '_' + node.property.name;
2669
- if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
2995
+ if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
2670
2996
 
2671
2997
  return {
2672
2998
  type: 'Identifier',
@@ -2723,13 +3049,13 @@ const generateFunc = (scope, decl) => {
2723
3049
  const func = {
2724
3050
  name,
2725
3051
  params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
2726
- returns: innerScope.returns,
2727
- locals: innerScope.locals,
2728
- throws: innerScope.throws,
2729
- index: currentFuncIndex++
3052
+ index: currentFuncIndex++,
3053
+ ...innerScope
2730
3054
  };
2731
3055
  funcIndex[name] = func.index;
2732
3056
 
3057
+ if (name === 'main') func.gotLastType = true;
3058
+
2733
3059
  // quick hack fixes
2734
3060
  for (const inst of wasm) {
2735
3061
  if (inst[0] === Opcodes.call && inst[1] === -1) {
@@ -2764,6 +3090,16 @@ const generateCode = (scope, decl) => {
2764
3090
  };
2765
3091
 
2766
3092
  const internalConstrs = {
3093
+ Boolean: {
3094
+ generate: (scope, decl) => {
3095
+ if (decl.arguments.length === 0) return number(0);
3096
+
3097
+ // should generate/run all args
3098
+ return truthy(scope, generate(scope, decl.arguments[0]), getNodeType(scope, decl.arguments[0]), false, false);
3099
+ },
3100
+ type: TYPES.boolean
3101
+ },
3102
+
2767
3103
  Array: {
2768
3104
  generate: (scope, decl, global, name) => {
2769
3105
  // new Array(i0, i1, ...)
@@ -2885,7 +3221,7 @@ export default program => {
2885
3221
  body: program.body
2886
3222
  };
2887
3223
 
2888
- if (process.argv.includes('-ast-log')) console.log(program.body.body);
3224
+ if (Prefs.astLog) console.log(program.body.body);
2889
3225
 
2890
3226
  generateFunc(scope, program);
2891
3227