porffor 0.2.0-4035760 → 0.2.0-4b72c49

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.
@@ -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;
@@ -160,7 +161,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
160
161
 
161
162
  case 'TaggedTemplateExpression': {
162
163
  const funcs = {
163
- asm: str => {
164
+ __Porffor_wasm: str => {
164
165
  let out = [];
165
166
 
166
167
  for (const line of str.split('\n')) {
@@ -168,8 +169,8 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
168
169
  if (asm[0] === '') continue; // blank
169
170
 
170
171
  if (asm[0] === 'local') {
171
- const [ name, idx, type ] = asm.slice(1);
172
- 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] };
173
174
  continue;
174
175
  }
175
176
 
@@ -179,7 +180,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
179
180
  }
180
181
 
181
182
  if (asm[0] === 'memory') {
182
- allocPage('asm instrinsic');
183
+ allocPage(scope, 'asm instrinsic');
183
184
  // todo: add to store/load offset insts
184
185
  continue;
185
186
  }
@@ -188,7 +189,11 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
188
189
  if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
189
190
 
190
191
  if (!Array.isArray(inst)) inst = [ inst ];
191
- 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
+ });
192
197
 
193
198
  out.push([ ...inst, ...immediates ]);
194
199
  }
@@ -196,31 +201,40 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
196
201
  return out;
197
202
  },
198
203
 
199
- __internal_print_type: str => {
200
- const type = getType(scope, str) - TYPES.number;
204
+ __Porffor_bs: str => [
205
+ ...makeString(scope, str, undefined, undefined, true),
201
206
 
202
- return [
203
- ...number(type),
204
- [ 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),
205
212
 
206
- // newline
207
- ...number(10),
208
- [ Opcodes.call, importedFuncs.printChar ]
209
- ];
210
- }
211
- }
213
+ ...number(TYPES.string, Valtype.i32),
214
+ setLastType(scope)
215
+ ],
216
+ };
212
217
 
213
218
  const name = decl.tag.name;
214
219
  // hack for inline asm
215
220
  if (!funcs[name]) return todo('tagged template expressions not implemented');
216
221
 
217
- 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
+
218
231
  return funcs[name](str);
219
232
  }
220
233
 
221
234
  default:
222
- if (decl.type.startsWith('TS')) {
223
- // ignore typescript nodes
235
+ // ignore typescript nodes
236
+ if (decl.type.startsWith('TS') ||
237
+ decl.type === 'ImportDeclaration' && decl.importKind === 'type') {
224
238
  return [];
225
239
  }
226
240
 
@@ -274,25 +288,25 @@ const generateIdent = (scope, decl) => {
274
288
  const name = mapName(rawName);
275
289
  let local = scope.locals[rawName];
276
290
 
277
- if (builtinVars[name]) {
291
+ if (Object.hasOwn(builtinVars, name)) {
278
292
  if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
279
293
  return builtinVars[name];
280
294
  }
281
295
 
282
- if (builtinFuncs[name] || internalConstrs[name]) {
296
+ if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
283
297
  // todo: return an actual something
284
298
  return number(1);
285
299
  }
286
300
 
287
- if (local === undefined) {
301
+ if (local?.idx === undefined) {
288
302
  // no local var with name
289
- if (importedFuncs.hasOwnProperty(name)) return number(importedFuncs[name]);
290
- if (funcIndex[name] !== undefined) return number(funcIndex[name]);
303
+ if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
304
+ if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name]);
291
305
 
292
- if (globals[name] !== undefined) return [ [ Opcodes.global_get, globals[name].idx ] ];
306
+ if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
293
307
  }
294
308
 
295
- if (local === undefined && rawName.startsWith('__')) {
309
+ if (local?.idx === undefined && rawName.startsWith('__')) {
296
310
  // return undefined if unknown key in already known var
297
311
  let parent = rawName.slice(2).split('_').slice(0, -1).join('_');
298
312
  if (parent.includes('_')) parent = '__' + parent;
@@ -301,7 +315,7 @@ const generateIdent = (scope, decl) => {
301
315
  if (!parentLookup[1]) return number(UNDEFINED);
302
316
  }
303
317
 
304
- if (local === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
318
+ if (local?.idx === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
305
319
 
306
320
  return [ [ Opcodes.local_get, local.idx ] ];
307
321
  };
@@ -409,9 +423,6 @@ const concatStrings = (scope, left, right, global, name, assign) => {
409
423
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
410
424
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
411
425
 
412
- const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
413
- if (aotWFA) addVarMeta(name, { wellFormed: undefined });
414
-
415
426
  if (assign) {
416
427
  const pointer = arrays.get(name ?? '$undeclared');
417
428
 
@@ -649,11 +660,12 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
649
660
  ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
650
661
  ];
651
662
 
652
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
663
+ const useTmp = knownType(scope, type) == null;
664
+ const tmp = useTmp && localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
653
665
 
654
666
  const def = [
655
667
  // if value != 0
656
- [ Opcodes.local_get, tmp ],
668
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
657
669
 
658
670
  // ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
659
671
  ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
@@ -665,7 +677,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
665
677
 
666
678
  return [
667
679
  ...wasm,
668
- [ Opcodes.local_set, tmp ],
680
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
669
681
 
670
682
  ...typeSwitch(scope, type, {
671
683
  // [TYPES.number]: def,
@@ -674,7 +686,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
674
686
  ...number(1, intOut ? Valtype.i32 : valtypeBinary)
675
687
  ],
676
688
  [TYPES.string]: [
677
- [ Opcodes.local_get, tmp ],
689
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
678
690
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
679
691
 
680
692
  // get length
@@ -686,7 +698,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
686
698
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
687
699
  ],
688
700
  [TYPES._bytestring]: [ // duplicate of string
689
- [ Opcodes.local_get, tmp ],
701
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
690
702
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
691
703
 
692
704
  // get length
@@ -700,10 +712,12 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
700
712
  };
701
713
 
702
714
  const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
703
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
715
+ const useTmp = knownType(scope, type) == null;
716
+ const tmp = useTmp && localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
717
+
704
718
  return [
705
719
  ...wasm,
706
- [ Opcodes.local_set, tmp ],
720
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
707
721
 
708
722
  ...typeSwitch(scope, type, {
709
723
  [TYPES._array]: [
@@ -711,7 +725,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
711
725
  ...number(0, intOut ? Valtype.i32 : valtypeBinary)
712
726
  ],
713
727
  [TYPES.string]: [
714
- [ Opcodes.local_get, tmp ],
728
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
715
729
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
716
730
 
717
731
  // get length
@@ -722,7 +736,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
722
736
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
723
737
  ],
724
738
  [TYPES._bytestring]: [ // duplicate of string
725
- [ Opcodes.local_get, tmp ],
739
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
726
740
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
727
741
 
728
742
  // get length
@@ -734,7 +748,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
734
748
  ],
735
749
  default: [
736
750
  // if value == 0
737
- [ Opcodes.local_get, tmp ],
751
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
738
752
 
739
753
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
740
754
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -744,10 +758,12 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
744
758
  };
745
759
 
746
760
  const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
747
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
761
+ const useTmp = knownType(scope, type) == null;
762
+ const tmp = useTmp && localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
763
+
748
764
  return [
749
765
  ...wasm,
750
- [ Opcodes.local_set, tmp ],
766
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
751
767
 
752
768
  ...typeSwitch(scope, type, {
753
769
  [TYPES.undefined]: [
@@ -756,7 +772,7 @@ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
756
772
  ],
757
773
  [TYPES.object]: [
758
774
  // object, null if == 0
759
- [ Opcodes.local_get, tmp ],
775
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
760
776
 
761
777
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
762
778
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -785,11 +801,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
785
801
  return performLogicOp(scope, op, left, right, leftType, rightType);
786
802
  }
787
803
 
804
+ const knownLeft = knownType(scope, leftType);
805
+ const knownRight = knownType(scope, rightType);
806
+
788
807
  const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
789
808
  const strictOp = op === '===' || op === '!==';
790
809
 
791
810
  const startOut = [], endOut = [];
792
- const finalise = out => startOut.concat(out, endOut);
811
+ const finalize = out => startOut.concat(out, endOut);
793
812
 
794
813
  // if strict (in)equal check types match
795
814
  if (strictOp) {
@@ -834,31 +853,32 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
834
853
  // todo: if equality op and an operand is undefined, return false
835
854
  // todo: niche null hell with 0
836
855
 
837
- // if (leftType === TYPES.string || rightType === TYPES.string) {
838
- // if (op === '+') {
839
- // // string concat (a + b)
840
- // return finalise(concatStrings(scope, left, right, _global, _name, assign));
841
- // }
842
-
843
- // // not an equality op, NaN
844
- // if (!eqOp) return finalise(number(NaN));
845
-
846
- // // else leave bool ops
847
- // // todo: convert string to number if string and number/bool
848
- // // todo: string (>|>=|<|<=) string
849
-
850
- // // string comparison
851
- // if (op === '===' || op === '==') {
852
- // return finalise(compareStrings(scope, left, right));
853
- // }
854
-
855
- // if (op === '!==' || op === '!=') {
856
- // return finalise([
857
- // ...compareStrings(scope, left, right),
858
- // [ Opcodes.i32_eqz ]
859
- // ]);
860
- // }
861
- // }
856
+ // todo: this should be dynamic but for now only static
857
+ if (knownLeft === TYPES.string || knownRight === TYPES.string) {
858
+ if (op === '+') {
859
+ // string concat (a + b)
860
+ return concatStrings(scope, left, right, _global, _name, assign);
861
+ }
862
+
863
+ // not an equality op, NaN
864
+ if (!eqOp) return number(NaN);
865
+
866
+ // else leave bool ops
867
+ // todo: convert string to number if string and number/bool
868
+ // todo: string (>|>=|<|<=) string
869
+
870
+ // string comparison
871
+ if (op === '===' || op === '==') {
872
+ return compareStrings(scope, left, right);
873
+ }
874
+
875
+ if (op === '!==' || op === '!=') {
876
+ return [
877
+ ...compareStrings(scope, left, right),
878
+ [ Opcodes.i32_eqz ]
879
+ ];
880
+ }
881
+ }
862
882
 
863
883
  let ops = operatorOpcode[valtype][op];
864
884
 
@@ -868,7 +888,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
868
888
  includeBuiltin(scope, builtinName);
869
889
  const idx = funcIndex[builtinName];
870
890
 
871
- return finalise([
891
+ return finalize([
872
892
  ...left,
873
893
  ...right,
874
894
  [ Opcodes.call, idx ]
@@ -883,9 +903,6 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
883
903
  let tmpLeft, tmpRight;
884
904
  // if equal op, check if strings for compareStrings
885
905
  if (op === '===' || op === '==' || op === '!==' || op === '!=') (() => {
886
- const knownLeft = knownType(scope, leftType);
887
- const knownRight = knownType(scope, rightType);
888
-
889
906
  // todo: intelligent partial skip later
890
907
  // if neither known are string, stop this madness
891
908
  if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
@@ -925,7 +942,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
925
942
  [ Opcodes.i32_or ],
926
943
  [ Opcodes.if, Blocktype.void ],
927
944
  ...number(0, Valtype.i32),
928
- [ Opcodes.br, 1 ],
945
+ [ Opcodes.br, 2 ],
929
946
  [ Opcodes.end ],
930
947
 
931
948
  ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
@@ -943,7 +960,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
943
960
  // }
944
961
  })();
945
962
 
946
- return finalise([
963
+ return finalize([
947
964
  ...left,
948
965
  ...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
949
966
  ...right,
@@ -960,7 +977,7 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
960
977
  return out;
961
978
  };
962
979
 
963
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [] }) => {
980
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [] }) => {
964
981
  const existing = funcs.find(x => x.name === name);
965
982
  if (existing) return existing;
966
983
 
@@ -972,6 +989,12 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
972
989
  locals[nameParam(i)] = { idx: i, type: allLocals[i] };
973
990
  }
974
991
 
992
+ for (const x of _data) {
993
+ const copy = { ...x };
994
+ copy.offset += pages.size * pageSize;
995
+ data.push(copy);
996
+ }
997
+
975
998
  if (typeof wasm === 'function') {
976
999
  const scope = {
977
1000
  name,
@@ -981,7 +1004,18 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
981
1004
  localInd: allLocals.length,
982
1005
  };
983
1006
 
984
- wasm = wasm(scope, { TYPES, typeSwitch, makeArray });
1007
+ wasm = wasm(scope, {
1008
+ TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage,
1009
+ builtin: name => {
1010
+ let idx = funcIndex[name] ?? importedFuncs[name];
1011
+ if (idx === undefined && builtinFuncs[name]) {
1012
+ includeBuiltin(scope, name);
1013
+ idx = funcIndex[name];
1014
+ }
1015
+
1016
+ return idx;
1017
+ }
1018
+ });
985
1019
  }
986
1020
 
987
1021
  let baseGlobalIdx, i = 0;
@@ -1086,7 +1120,10 @@ const TYPE_NAMES = {
1086
1120
  const getType = (scope, _name) => {
1087
1121
  const name = mapName(_name);
1088
1122
 
1123
+ if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
1089
1124
  if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
1125
+
1126
+ if (typedInput && globals[name]?.metadata?.type != null) return number(globals[name].metadata.type, Valtype.i32);
1090
1127
  if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
1091
1128
 
1092
1129
  let type = TYPES.undefined;
@@ -1164,7 +1201,7 @@ const getNodeType = (scope, node) => {
1164
1201
  if (func.returnType) return func.returnType;
1165
1202
  }
1166
1203
 
1167
- if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1204
+ if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1168
1205
  if (internalConstrs[name]) return internalConstrs[name].type;
1169
1206
 
1170
1207
  // check if this is a prototype function
@@ -1179,6 +1216,11 @@ const getNodeType = (scope, node) => {
1179
1216
  if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1180
1217
  }
1181
1218
 
1219
+ if (name.startsWith('__Porffor_wasm_')) {
1220
+ // todo: return undefined for non-returning ops
1221
+ return TYPES.number;
1222
+ }
1223
+
1182
1224
  if (scope.locals['#last_type']) return [ getLastType(scope) ];
1183
1225
 
1184
1226
  // presume
@@ -1227,6 +1269,14 @@ const getNodeType = (scope, node) => {
1227
1269
 
1228
1270
  if (node.type === 'BinaryExpression') {
1229
1271
  if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
1272
+ if (node.operator !== '+') return TYPES.number;
1273
+
1274
+ const knownLeft = knownType(scope, getNodeType(scope, node.left));
1275
+ const knownRight = knownType(scope, getNodeType(scope, node.right));
1276
+
1277
+ // todo: this should be dynamic but for now only static
1278
+ if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
1279
+
1230
1280
  return TYPES.number;
1231
1281
 
1232
1282
  // todo: string concat types
@@ -1251,7 +1301,7 @@ const getNodeType = (scope, node) => {
1251
1301
  if (node.operator === '!') return TYPES.boolean;
1252
1302
  if (node.operator === 'void') return TYPES.undefined;
1253
1303
  if (node.operator === 'delete') return TYPES.boolean;
1254
- if (node.operator === 'typeof') return process.argv.includes('-bytestring') ? TYPES._bytestring : TYPES.string;
1304
+ if (node.operator === 'typeof') return Prefs.bytestring ? TYPES._bytestring : TYPES.string;
1255
1305
 
1256
1306
  return TYPES.number;
1257
1307
  }
@@ -1286,8 +1336,8 @@ const getNodeType = (scope, node) => {
1286
1336
  const generateLiteral = (scope, decl, global, name) => {
1287
1337
  if (decl.value === null) return number(NULL);
1288
1338
 
1339
+ // hack: just return 1 for regex literals
1289
1340
  if (decl.regex) {
1290
- scope.regex[name] = decl.regex;
1291
1341
  return number(1);
1292
1342
  }
1293
1343
 
@@ -1473,8 +1523,8 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1473
1523
  // literal.func()
1474
1524
  if (!name && decl.callee.type === 'MemberExpression') {
1475
1525
  // megahack for /regex/.func()
1476
- if (decl.callee.object.regex) {
1477
- const funcName = decl.callee.property.name;
1526
+ const funcName = decl.callee.property.name;
1527
+ if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
1478
1528
  const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1479
1529
 
1480
1530
  funcIndex[func.name] = func.index;
@@ -1645,6 +1695,32 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1645
1695
  idx = -1;
1646
1696
  }
1647
1697
 
1698
+ if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1699
+ const wasmOps = {
1700
+ // pointer, align, offset
1701
+ i32_load8_u: { imms: 2, args: 1 },
1702
+ // pointer, value, align, offset
1703
+ i32_store8: { imms: 2, args: 2 },
1704
+ };
1705
+
1706
+ const opName = name.slice('__Porffor_wasm_'.length);
1707
+
1708
+ if (wasmOps[opName]) {
1709
+ const op = wasmOps[opName];
1710
+
1711
+ const argOut = [];
1712
+ for (let i = 0; i < op.args; i++) argOut.push(...generate(scope, decl.arguments[i]));
1713
+
1714
+ // literals only
1715
+ const imms = decl.arguments.slice(op.args).map(x => x.value);
1716
+
1717
+ return [
1718
+ ...argOut,
1719
+ [ Opcodes[opName], ...imms ]
1720
+ ];
1721
+ }
1722
+ }
1723
+
1648
1724
  if (idx === undefined) {
1649
1725
  if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`);
1650
1726
  return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
@@ -1654,7 +1730,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1654
1730
 
1655
1731
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1656
1732
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1657
- const typedReturn = userFunc || builtinFuncs[name]?.typedReturn;
1733
+ const typedReturns = userFunc || builtinFuncs[name]?.typedReturns;
1658
1734
  const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1659
1735
 
1660
1736
  let args = decl.arguments;
@@ -1671,14 +1747,20 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1671
1747
  if (func && func.throws) scope.throws = true;
1672
1748
 
1673
1749
  let out = [];
1674
- for (const arg of args) {
1750
+ for (let i = 0; i < args.length; i++) {
1751
+ const arg = args[i];
1675
1752
  out = out.concat(generate(scope, arg));
1753
+
1754
+ if (builtinFuncs[name] && builtinFuncs[name].params[i] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1755
+ out.push(Opcodes.i32_to);
1756
+ }
1757
+
1676
1758
  if (typedParams) out = out.concat(getNodeType(scope, arg));
1677
1759
  }
1678
1760
 
1679
1761
  out.push([ Opcodes.call, idx ]);
1680
1762
 
1681
- if (!typedReturn) {
1763
+ if (!typedReturns) {
1682
1764
  // let type;
1683
1765
  // if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
1684
1766
  // if (internalConstrs[name]) type = internalConstrs[name].type;
@@ -1690,6 +1772,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1690
1772
  // );
1691
1773
  } else out.push(setLastType(scope));
1692
1774
 
1775
+ if (builtinFuncs[name] && builtinFuncs[name].returns[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1776
+ out.push(Opcodes.i32_from);
1777
+ }
1778
+
1693
1779
  return out;
1694
1780
  };
1695
1781
 
@@ -1814,14 +1900,14 @@ const brTable = (input, bc, returns) => {
1814
1900
  };
1815
1901
 
1816
1902
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1817
- if (!process.argv.includes('-bytestring')) delete bc[TYPES._bytestring];
1903
+ if (!Prefs.bytestring) delete bc[TYPES._bytestring];
1818
1904
 
1819
1905
  const known = knownType(scope, type);
1820
1906
  if (known != null) {
1821
1907
  return bc[known] ?? bc.default;
1822
1908
  }
1823
1909
 
1824
- if (process.argv.includes('-typeswitch-use-brtable'))
1910
+ if (Prefs.typeswitchUseBrtable)
1825
1911
  return brTable(type, bc, returns);
1826
1912
 
1827
1913
  const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
@@ -1856,7 +1942,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1856
1942
  return out;
1857
1943
  };
1858
1944
 
1859
- const allocVar = (scope, name, global = false) => {
1945
+ const allocVar = (scope, name, global = false, type = true) => {
1860
1946
  const target = global ? globals : scope.locals;
1861
1947
 
1862
1948
  // already declared
@@ -1870,8 +1956,10 @@ const allocVar = (scope, name, global = false) => {
1870
1956
  let idx = global ? globalInd++ : scope.localInd++;
1871
1957
  target[name] = { idx, type: valtypeBinary };
1872
1958
 
1873
- let typeIdx = global ? globalInd++ : scope.localInd++;
1874
- target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
1959
+ if (type) {
1960
+ let typeIdx = global ? globalInd++ : scope.localInd++;
1961
+ target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
1962
+ }
1875
1963
 
1876
1964
  return idx;
1877
1965
  };
@@ -1891,6 +1979,7 @@ const typeAnnoToPorfType = x => {
1891
1979
 
1892
1980
  switch (x) {
1893
1981
  case 'i32':
1982
+ case 'i64':
1894
1983
  return TYPES.number;
1895
1984
  }
1896
1985
 
@@ -1914,7 +2003,7 @@ const extractTypeAnnotation = decl => {
1914
2003
  const typeName = type;
1915
2004
  type = typeAnnoToPorfType(type);
1916
2005
 
1917
- if (type === TYPES._bytestring && !process.argv.includes('-bytestring')) type = TYPES.string;
2006
+ if (type === TYPES._bytestring && !Prefs.bytestring) type = TYPES.string;
1918
2007
 
1919
2008
  // if (decl.name) console.log(decl.name, { type, elementType });
1920
2009
 
@@ -1932,6 +2021,8 @@ const generateVar = (scope, decl) => {
1932
2021
  for (const x of decl.declarations) {
1933
2022
  const name = mapName(x.id.name);
1934
2023
 
2024
+ if (!name) return todo('destructuring is not supported yet');
2025
+
1935
2026
  if (x.init && isFuncType(x.init.type)) {
1936
2027
  // hack for let a = function () { ... }
1937
2028
  x.init.id = { name };
@@ -1947,7 +2038,7 @@ const generateVar = (scope, decl) => {
1947
2038
  continue; // always ignore
1948
2039
  }
1949
2040
 
1950
- let idx = allocVar(scope, name, global);
2041
+ let idx = allocVar(scope, name, global, !x.id.typeAnnotation);
1951
2042
 
1952
2043
  if (typedInput && x.id.typeAnnotation) {
1953
2044
  addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
@@ -2070,6 +2161,8 @@ const generateAssign = (scope, decl) => {
2070
2161
  ];
2071
2162
  }
2072
2163
 
2164
+ if (!name) return todo('destructuring is not supported yet');
2165
+
2073
2166
  const [ local, isGlobal ] = lookupName(scope, name);
2074
2167
 
2075
2168
  if (local === undefined) {
@@ -2116,9 +2209,7 @@ const generateAssign = (scope, decl) => {
2116
2209
  ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
2117
2210
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
2118
2211
 
2119
- getLastType(scope),
2120
- // hack: type is idx+1
2121
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2212
+ ...setType(scope, name, getLastType(scope))
2122
2213
  ];
2123
2214
  }
2124
2215
 
@@ -2129,9 +2220,7 @@ const generateAssign = (scope, decl) => {
2129
2220
 
2130
2221
  // todo: string concat types
2131
2222
 
2132
- // hack: type is idx+1
2133
- ...number(TYPES.number, Valtype.i32),
2134
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2223
+ ...setType(scope, name, TYPES.number)
2135
2224
  ];
2136
2225
  };
2137
2226
 
@@ -2315,8 +2404,10 @@ const generateFor = (scope, decl) => {
2315
2404
  out.push([ Opcodes.loop, Blocktype.void ]);
2316
2405
  depth.push('for');
2317
2406
 
2318
- out.push(...generate(scope, decl.test));
2319
- out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
2407
+ if (decl.test) out.push(...generate(scope, decl.test), Opcodes.i32_to);
2408
+ else out.push(...number(1, Valtype.i32));
2409
+
2410
+ out.push([ Opcodes.if, Blocktype.void ]);
2320
2411
  depth.push('if');
2321
2412
 
2322
2413
  out.push([ Opcodes.block, Blocktype.void ]);
@@ -2324,8 +2415,7 @@ const generateFor = (scope, decl) => {
2324
2415
  out.push(...generate(scope, decl.body));
2325
2416
  out.push([ Opcodes.end ]);
2326
2417
 
2327
- out.push(...generate(scope, decl.update));
2328
- depth.pop();
2418
+ if (decl.update) out.push(...generate(scope, decl.update));
2329
2419
 
2330
2420
  out.push([ Opcodes.br, 1 ]);
2331
2421
  out.push([ Opcodes.end ], [ Opcodes.end ]);
@@ -2382,7 +2472,13 @@ const generateForOf = (scope, decl) => {
2382
2472
  // setup local for left
2383
2473
  generate(scope, decl.left);
2384
2474
 
2385
- const leftName = decl.left.declarations[0].id.name;
2475
+ let leftName = decl.left.declarations?.[0]?.id?.name;
2476
+ if (!leftName && decl.left.name) {
2477
+ leftName = decl.left.name;
2478
+
2479
+ generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
2480
+ }
2481
+
2386
2482
  const [ local, isGlobal ] = lookupName(scope, leftName);
2387
2483
 
2388
2484
  depth.push('block');
@@ -2535,6 +2631,9 @@ const generateThrow = (scope, decl) => {
2535
2631
  let exceptId = exceptions.push({ constructor, message }) - 1;
2536
2632
  let tagIdx = tags[0].idx;
2537
2633
 
2634
+ scope.exceptions ??= [];
2635
+ scope.exceptions.push(exceptId);
2636
+
2538
2637
  // todo: write a description of how this works lol
2539
2638
 
2540
2639
  return [
@@ -2579,7 +2678,7 @@ const generateAssignPat = (scope, decl) => {
2579
2678
  };
2580
2679
 
2581
2680
  let pages = new Map();
2582
- const allocPage = (reason, type) => {
2681
+ const allocPage = (scope, reason, type) => {
2583
2682
  if (pages.has(reason)) return pages.get(reason).ind;
2584
2683
 
2585
2684
  if (reason.startsWith('array:')) pages.hasArray = true;
@@ -2590,16 +2689,20 @@ const allocPage = (reason, type) => {
2590
2689
  const ind = pages.size;
2591
2690
  pages.set(reason, { ind, type });
2592
2691
 
2593
- if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
2692
+ scope.pages ??= new Map();
2693
+ scope.pages.set(reason, { ind, type });
2694
+
2695
+ if (Prefs.allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
2594
2696
 
2595
2697
  return ind;
2596
2698
  };
2597
2699
 
2700
+ // todo: add scope.pages
2598
2701
  const freePage = reason => {
2599
2702
  const { ind } = pages.get(reason);
2600
2703
  pages.delete(reason);
2601
2704
 
2602
- if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
2705
+ if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
2603
2706
 
2604
2707
  return ind;
2605
2708
  };
@@ -2625,15 +2728,14 @@ const StoreOps = {
2625
2728
 
2626
2729
  let data = [];
2627
2730
 
2628
- const compileBytes = (val, itemType, signed = true) => {
2731
+ const compileBytes = (val, itemType) => {
2629
2732
  // todo: this is a mess and needs confirming / ????
2630
2733
  switch (itemType) {
2631
2734
  case 'i8': return [ val % 256 ];
2632
- case 'i16': return [ val % 256, Math.floor(val / 256) ];
2633
-
2634
- case 'i32':
2635
- case 'i64':
2636
- return enforceFourBytes(signedLEB128(val));
2735
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
2736
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
2737
+ case 'i32': return [...new Uint8Array(new Int32Array([ val ]).buffer)];
2738
+ // todo: i64
2637
2739
 
2638
2740
  case 'f64': return ieee754_binary64(val);
2639
2741
  }
@@ -2657,7 +2759,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2657
2759
 
2658
2760
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2659
2761
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2660
- arrays.set(name, allocPage(`${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2762
+ arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2661
2763
  }
2662
2764
 
2663
2765
  const pointer = arrays.get(name);
@@ -2668,19 +2770,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2668
2770
  const valtype = itemTypeToValtype[itemType];
2669
2771
  const length = elements.length;
2670
2772
 
2671
- if (firstAssign && useRawElements) {
2672
- let bytes = compileBytes(length, 'i32');
2773
+ if (firstAssign && useRawElements && !Prefs.noData) {
2774
+ // if length is 0 memory/data will just be 0000... anyway
2775
+ if (length !== 0) {
2776
+ let bytes = compileBytes(length, 'i32');
2673
2777
 
2674
- if (!initEmpty) for (let i = 0; i < length; i++) {
2675
- if (elements[i] == null) continue;
2778
+ if (!initEmpty) for (let i = 0; i < length; i++) {
2779
+ if (elements[i] == null) continue;
2676
2780
 
2677
- bytes.push(...compileBytes(elements[i], itemType));
2678
- }
2781
+ bytes.push(...compileBytes(elements[i], itemType));
2782
+ }
2679
2783
 
2680
- data.push({
2681
- offset: pointer,
2682
- bytes
2683
- });
2784
+ const ind = data.push({
2785
+ offset: pointer,
2786
+ bytes
2787
+ }) - 1;
2788
+
2789
+ scope.data ??= [];
2790
+ scope.data.push(ind);
2791
+ }
2684
2792
 
2685
2793
  // local value as pointer
2686
2794
  out.push(...number(pointer));
@@ -2714,7 +2822,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2714
2822
  };
2715
2823
 
2716
2824
  const byteStringable = str => {
2717
- if (!process.argv.includes('-bytestring')) return false;
2825
+ if (!Prefs.bytestring) return false;
2718
2826
 
2719
2827
  for (let i = 0; i < str.length; i++) {
2720
2828
  if (str.charCodeAt(i) > 0xFF) return false;
@@ -2723,9 +2831,9 @@ const byteStringable = str => {
2723
2831
  return true;
2724
2832
  };
2725
2833
 
2726
- const makeString = (scope, str, global = false, name = '$undeclared') => {
2834
+ const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
2727
2835
  const rawElements = new Array(str.length);
2728
- let byteStringable = process.argv.includes('-bytestring');
2836
+ let byteStringable = Prefs.bytestring;
2729
2837
  for (let i = 0; i < str.length; i++) {
2730
2838
  const c = str.charCodeAt(i);
2731
2839
  rawElements[i] = c;
@@ -2733,6 +2841,8 @@ const makeString = (scope, str, global = false, name = '$undeclared') => {
2733
2841
  if (byteStringable && c > 0xFF) byteStringable = false;
2734
2842
  }
2735
2843
 
2844
+ if (byteStringable && forceBytestring === false) byteStringable = false;
2845
+
2736
2846
  return makeArray(scope, {
2737
2847
  rawElements
2738
2848
  }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
@@ -2766,8 +2876,6 @@ export const generateMember = (scope, decl, _global, _name) => {
2766
2876
  const object = generate(scope, decl.object);
2767
2877
  const property = generate(scope, decl.property);
2768
2878
 
2769
- console.log(decl.property, property);
2770
-
2771
2879
  // // todo: we should only do this for strings but we don't know at compile-time :(
2772
2880
  // hack: this is naughty and will break things!
2773
2881
  let newOut = number(0, valtypeBinary), newPointer = -1;
@@ -2860,7 +2968,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2860
2968
  setLastType(scope)
2861
2969
  ],
2862
2970
 
2863
- default: [ [ Opcodes.unreachable ] ]
2971
+ default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
2864
2972
  });
2865
2973
  };
2866
2974
 
@@ -2886,7 +2994,7 @@ const objectHack = node => {
2886
2994
  if (!objectName) return node;
2887
2995
 
2888
2996
  const name = '__' + objectName + '_' + node.property.name;
2889
- if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
2997
+ if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
2890
2998
 
2891
2999
  return {
2892
3000
  type: 'Identifier',
@@ -2948,6 +3056,8 @@ const generateFunc = (scope, decl) => {
2948
3056
  };
2949
3057
  funcIndex[name] = func.index;
2950
3058
 
3059
+ if (name === 'main') func.gotLastType = true;
3060
+
2951
3061
  // quick hack fixes
2952
3062
  for (const inst of wasm) {
2953
3063
  if (inst[0] === Opcodes.call && inst[1] === -1) {
@@ -2982,6 +3092,16 @@ const generateCode = (scope, decl) => {
2982
3092
  };
2983
3093
 
2984
3094
  const internalConstrs = {
3095
+ Boolean: {
3096
+ generate: (scope, decl) => {
3097
+ if (decl.arguments.length === 0) return number(0);
3098
+
3099
+ // should generate/run all args
3100
+ return truthy(scope, generate(scope, decl.arguments[0]), getNodeType(scope, decl.arguments[0]), false, false);
3101
+ },
3102
+ type: TYPES.boolean
3103
+ },
3104
+
2985
3105
  Array: {
2986
3106
  generate: (scope, decl, global, name) => {
2987
3107
  // new Array(i0, i1, ...)
@@ -3103,7 +3223,7 @@ export default program => {
3103
3223
  body: program.body
3104
3224
  };
3105
3225
 
3106
- if (process.argv.includes('-ast-log')) console.log(program.body.body);
3226
+ if (Prefs.astLog) console.log(program.body.body);
3107
3227
 
3108
3228
  generateFunc(scope, program);
3109
3229