porffor 0.17.0-30b9c5fa5 → 0.17.0-3d806f394

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.
@@ -123,6 +123,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
123
123
  case 'EmptyStatement':
124
124
  return generateEmpty(scope, decl);
125
125
 
126
+ case 'MetaProperty':
127
+ return generateMeta(scope, decl);
128
+
126
129
  case 'ConditionalExpression':
127
130
  return generateConditional(scope, decl);
128
131
 
@@ -295,10 +298,10 @@ const generateIdent = (scope, decl) => {
295
298
  }
296
299
 
297
300
  // todo: enable this by default in future
298
- // if (!Object.hasOwn(funcIndex, name) && Object.hasOwn(builtinFuncs, name)) {
299
- // includeBuiltin(scope, name);
300
- // return number(funcIndex[name] - importedFuncs.length);
301
- // }
301
+ if (!Object.hasOwn(funcIndex, name) && Object.hasOwn(builtinFuncs, name)) {
302
+ includeBuiltin(scope, name);
303
+ return number(funcIndex[name] - importedFuncs.length);
304
+ }
302
305
 
303
306
  if (isExistingProtoFunc(name) || Object.hasOwn(internalConstrs, name) || Object.hasOwn(builtinFuncs, name)) {
304
307
  // todo: return an actual something
@@ -1026,12 +1029,13 @@ const asmFuncToAsm = (func, scope) => {
1026
1029
  idx = funcIndex[n];
1027
1030
  }
1028
1031
 
1032
+ if (idx == null) throw new Error(`builtin('${n}') failed to find a func (inside ${scope.name})`);
1029
1033
  return idx;
1030
1034
  }
1031
1035
  });
1032
1036
  };
1033
1037
 
1034
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits = [], returns, returnType, localNames = [], globalNames = [], data: _data = [], table = false }) => {
1038
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits = [], returns, returnType, localNames = [], globalNames = [], data: _data = [], table = false, constr = false }) => {
1035
1039
  const existing = funcs.find(x => x.name === name);
1036
1040
  if (existing) return existing;
1037
1041
 
@@ -1058,13 +1062,17 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1058
1062
  returnType,
1059
1063
  internal: true,
1060
1064
  index: currentFuncIndex++,
1061
- table
1065
+ table,
1066
+ constr
1062
1067
  };
1063
1068
 
1064
1069
  funcs.push(func);
1065
1070
  funcIndex[name] = func.index;
1066
1071
 
1067
- if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, func);
1072
+ if (typeof wasm === 'function') {
1073
+ if (globalThis.precompile) wasm = [];
1074
+ else wasm = asmFuncToAsm(wasm, func);
1075
+ }
1068
1076
 
1069
1077
  let baseGlobalIdx, i = 0;
1070
1078
  for (const type of globalTypes) {
@@ -1085,15 +1093,35 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1085
1093
 
1086
1094
  if (table) {
1087
1095
  for (const inst of wasm) {
1088
- if (inst[0] === Opcodes.i32_load16_u && inst.at(-1) === 'read_argc') {
1096
+ if (inst.at(-1) === 'read func lut') {
1089
1097
  inst.splice(2, 99);
1090
- inst.push(...unsignedLEB128(allocPage({}, 'func argc lut') * pageSize));
1098
+ inst.push(...unsignedLEB128(allocPage({}, 'func lut') * pageSize));
1091
1099
  }
1092
1100
  }
1093
1101
 
1094
1102
  funcs.table = true;
1095
1103
  }
1096
1104
 
1105
+ if (constr) {
1106
+ func.params = [...func.params];
1107
+ func.params.unshift(Valtype.i32);
1108
+
1109
+ // move all locals +1 idx (sigh)
1110
+ func.localInd++;
1111
+ const locals = func.locals;
1112
+ for (const x in locals) {
1113
+ locals[x].idx++;
1114
+ }
1115
+
1116
+ locals['#newtarget'] = { idx: 0, type: Valtype.i32 };
1117
+
1118
+ for (const inst of wasm) {
1119
+ if (inst[0] === Opcodes.local_get || inst[0] === Opcodes.local_set || inst[0] === Opcodes.local_tee) {
1120
+ inst[1]++;
1121
+ }
1122
+ }
1123
+ }
1124
+
1097
1125
  func.wasm = wasm;
1098
1126
 
1099
1127
  return func;
@@ -1220,20 +1248,7 @@ const getNodeType = (scope, node) => {
1220
1248
  return TYPES.number;
1221
1249
  }
1222
1250
 
1223
- if (node.type === 'NewExpression' && builtinFuncs[name + '$constructor']) {
1224
- if (builtinFuncs[name + '$constructor'].typedReturns) {
1225
- if (scope.locals['#last_type']) return getLastType(scope);
1226
-
1227
- // presume
1228
- // todo: warn here?
1229
- return TYPES.number;
1230
- }
1231
-
1232
- return builtinFuncs[name + '$constructor'].returnType ?? TYPES.number;
1233
- }
1234
-
1235
1251
  const func = funcs.find(x => x.name === name);
1236
-
1237
1252
  if (func) {
1238
1253
  if (func.returnType != null) return func.returnType;
1239
1254
  }
@@ -1531,7 +1546,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1531
1546
  name = func.name;
1532
1547
  }
1533
1548
 
1534
- if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
1549
+ if (!decl._new && name === 'eval' && decl.arguments[0]?.type === 'Literal') {
1535
1550
  // literal eval hack
1536
1551
  const code = decl.arguments[0]?.value ?? '';
1537
1552
 
@@ -1574,7 +1589,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1574
1589
 
1575
1590
  let protoName, target;
1576
1591
  // ident.func()
1577
- if (name && name.startsWith('__')) {
1592
+ if (!decl._new && name && name.startsWith('__')) {
1578
1593
  const spl = name.slice(2).split('_');
1579
1594
 
1580
1595
  protoName = spl[spl.length - 1];
@@ -1587,12 +1602,12 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1587
1602
  }
1588
1603
 
1589
1604
  // literal.func()
1590
- if (!name && decl.callee.type === 'MemberExpression') {
1605
+ if (!decl._new && !name && decl.callee.type === 'MemberExpression') {
1591
1606
  // megahack for /regex/.func()
1592
1607
  const funcName = decl.callee.property.name;
1593
- if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
1608
+ if (decl.callee.object.regex && ['test'].includes(funcName)) {
1594
1609
  const regex = decl.callee.object.regex.pattern;
1595
- const rhemynName = `regex_${funcName}_${regex}`;
1610
+ const rhemynName = `regex_${funcName}_${sanitize(regex)}`;
1596
1611
 
1597
1612
  if (!funcIndex[rhemynName]) {
1598
1613
  const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
@@ -1613,7 +1628,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1613
1628
  [ Opcodes.call, idx ],
1614
1629
  Opcodes.i32_from_u,
1615
1630
 
1616
- ...setLastType(scope, TYPES.boolean)
1631
+ ...setLastType(scope, Rhemyn.types[funcName])
1617
1632
  ];
1618
1633
  }
1619
1634
 
@@ -1622,27 +1637,56 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1622
1637
  target = decl.callee.object;
1623
1638
  }
1624
1639
 
1625
- // if (protoName && baseType === TYPES.string && Rhemyn[protoName]) {
1626
- // const func = Rhemyn[protoName](decl.arguments[0].regex.pattern, currentFuncIndex++);
1640
+ let out = [];
1641
+ if (protoName) {
1642
+ if (['search'].includes(protoName)) {
1643
+ const regex = decl.arguments[0]?.regex?.pattern;
1644
+ if (!regex) return [
1645
+ // no/bad regex arg, return -1/0 for now
1646
+ ...generate(scope, target),
1647
+ [ Opcodes.drop ],
1627
1648
 
1628
- // funcIndex[func.name] = func.index;
1629
- // funcs.push(func);
1649
+ ...number(Rhemyn.types[protoName] === TYPES.number ? -1 : 0),
1650
+ ...setLastType(scope, Rhemyn.types[protoName])
1651
+ ];
1630
1652
 
1631
- // return [
1632
- // generate(scope, decl.callee.object)
1653
+ const rhemynName = `regex_${protoName}_${sanitize(regex)}`;
1633
1654
 
1634
- // // call regex func
1635
- // [ Opcodes.call, func.index ],
1636
- // Opcodes.i32_from_u
1637
- // ];
1638
- // }
1655
+ if (!funcIndex[rhemynName]) {
1656
+ const func = Rhemyn[protoName](regex, currentFuncIndex++, rhemynName);
1657
+ func.internal = true;
1639
1658
 
1640
- if (protoName) {
1641
- const protoBC = {};
1659
+ funcIndex[func.name] = func.index;
1660
+ funcs.push(func);
1661
+ }
1662
+
1663
+ const idx = funcIndex[rhemynName];
1664
+ return [
1665
+ // make string arg
1666
+ ...generate(scope, target),
1667
+ Opcodes.i32_to_u,
1668
+ ...getNodeType(scope, target),
1669
+
1670
+ // call regex func
1671
+ [ Opcodes.call, idx ],
1672
+ Opcodes.i32_from,
1642
1673
 
1674
+ ...setLastType(scope, Rhemyn.types[protoName])
1675
+ ];
1676
+ }
1677
+
1678
+ const protoBC = {};
1643
1679
  const builtinProtoCands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + protoName));
1644
1680
 
1645
1681
  if (!decl._protoInternalCall && builtinProtoCands.length > 0) {
1682
+ out.push(
1683
+ ...generate(scope, target),
1684
+ [ Opcodes.local_set, localTmp(scope, '#proto_target') ],
1685
+
1686
+ ...getNodeType(scope, target),
1687
+ [ Opcodes.local_set, localTmp(scope, '#proto_target#type', Valtype.i32) ],
1688
+ );
1689
+
1646
1690
  for (const x of builtinProtoCands) {
1647
1691
  const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
1648
1692
  if (type == null) continue;
@@ -1652,7 +1696,14 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1652
1696
  type: 'Identifier',
1653
1697
  name: x
1654
1698
  },
1655
- arguments: [ target, ...decl.arguments ],
1699
+ arguments: [
1700
+ {
1701
+ type: 'Identifier',
1702
+ name: '#proto_target'
1703
+ },
1704
+
1705
+ ...decl.arguments
1706
+ ],
1656
1707
  _protoInternalCall: true
1657
1708
  });
1658
1709
  }
@@ -1746,29 +1797,37 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1746
1797
  }
1747
1798
 
1748
1799
  if (Object.keys(protoBC).length > 0) {
1749
- return typeSwitch(scope, getNodeType(scope, target), {
1750
- ...protoBC,
1800
+ return [
1801
+ ...out,
1751
1802
 
1752
- // TODO: error better
1753
- default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1754
- }, valtypeBinary);
1803
+ ...typeSwitch(scope, builtinProtoCands.length > 0 ? [ [ Opcodes.local_get, localTmp(scope, '#proto_target#type', Valtype.i32) ] ] : getNodeType(scope, target), {
1804
+ ...protoBC,
1805
+
1806
+ // TODO: error better
1807
+ default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1808
+ }, valtypeBinary)
1809
+ ];
1755
1810
  }
1756
1811
  }
1757
1812
 
1758
- // TODO: only allows callee as literal
1759
- if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
1813
+ // TODO: only allows callee as identifier
1814
+ if (!name) return todo(scope, `only identifier callees (got ${decl.callee.type})`);
1760
1815
 
1761
1816
  let idx = funcIndex[name] ?? importedFuncs[name];
1762
1817
  if (idx === undefined && builtinFuncs[name]) {
1763
1818
  if (builtinFuncs[name].floatOnly && valtype !== 'f64') throw new Error(`Cannot use built-in ${unhackName(name)} with integer valtype`);
1819
+ if (decl._new && !builtinFuncs[name].constr) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a constructor`, true);
1764
1820
 
1765
1821
  includeBuiltin(scope, name);
1766
1822
  idx = funcIndex[name];
1767
1823
  }
1768
1824
 
1769
- if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
1825
+ if (idx === undefined && internalConstrs[name]) {
1826
+ if (decl._new && internalConstrs[name].notConstr) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a constructor`, true);
1827
+ return internalConstrs[name].generate(scope, decl, _global, _name);
1828
+ }
1770
1829
 
1771
- if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1830
+ if (idx === undefined && !decl._new && name.startsWith('__Porffor_wasm_')) {
1772
1831
  const wasmOps = {
1773
1832
  // pointer, align, offset
1774
1833
  i32_load: { imms: 2, args: [ true ], returns: 1 },
@@ -1823,7 +1882,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1823
1882
 
1824
1883
  const indirectMode = Prefs.indirectCallMode ?? 'vararg';
1825
1884
  // options: vararg, strict
1826
- // - strict: simpler, smaller size usage, no func argc lut needed.
1885
+ // - strict: simpler, smaller size usage, no func lut needed.
1827
1886
  // ONLY works when arg count of call == arg count of function being called
1828
1887
  // - vararg: large size usage, cursed.
1829
1888
  // works when arg count of call != arg count of function being called*
@@ -1833,8 +1892,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1833
1892
  scope.table = true;
1834
1893
 
1835
1894
  let args = decl.arguments;
1836
- let out = [];
1837
-
1838
1895
  let locals = [];
1839
1896
 
1840
1897
  if (indirectMode === 'vararg') {
@@ -1898,24 +1955,67 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1898
1955
  // *for argc 0-3, in future (todo:) the max number should be
1899
1956
  // dynamically changed to the max argc of any func in the js file.
1900
1957
 
1901
- const funcLocal = localTmp(scope, `#indirect_func`, Valtype.i32);
1958
+ const funcLocal = localTmp(scope, '#indirect_func', Valtype.i32);
1959
+ const flags = localTmp(scope, '#indirect_flags', Valtype.i32);
1902
1960
 
1903
1961
  const gen = argc => {
1904
- const out = [];
1962
+ const argsOut = [];
1905
1963
  for (let i = 0; i < argc; i++) {
1906
- out.push(
1964
+ argsOut.push(
1907
1965
  [ Opcodes.local_get, locals[i][0] ],
1908
1966
  [ Opcodes.local_get, locals[i][1] ]
1909
1967
  );
1910
1968
  }
1911
1969
 
1912
- out.push(
1913
- [ Opcodes.local_get, funcLocal ],
1914
- [ Opcodes.call_indirect, argc, 0 ],
1915
- ...setLastType(scope)
1916
- )
1970
+ const checkFlag = (flag, pass, fail) => [
1971
+ [ Opcodes.local_get, flags ],
1972
+ ...number(flag, Valtype.i32),
1973
+ [ Opcodes.i32_and ],
1974
+ [ Opcodes.if, valtypeBinary ],
1975
+ ...pass,
1976
+ [ Opcodes.else ],
1977
+ ...fail,
1978
+ [ Opcodes.end ]
1979
+ ];
1917
1980
 
1918
- return out;
1981
+ // pain.
1982
+ // return checkFlag(0b10, [ // constr
1983
+ // [ Opcodes.i32_const, decl._new ? 1 : 0 ],
1984
+ // ...argsOut,
1985
+ // [ Opcodes.local_get, funcLocal ],
1986
+ // [ Opcodes.call_indirect, argc, 0, 'constr' ],
1987
+ // ...setLastType(scope),
1988
+ // ], [
1989
+ // ...argsOut,
1990
+ // [ Opcodes.local_get, funcLocal ],
1991
+ // [ Opcodes.call_indirect, argc, 0 ],
1992
+ // ...setLastType(scope),
1993
+ // ]);
1994
+
1995
+ return checkFlag(0b1, // no type return
1996
+ checkFlag(0b10, [ // no type return & constr
1997
+ [ Opcodes.i32_const, decl._new ? 1 : 0 ],
1998
+ ...argsOut,
1999
+ [ Opcodes.local_get, funcLocal ],
2000
+ [ Opcodes.call_indirect, argc, 0, 'no_type_return', 'constr' ],
2001
+ ], [
2002
+ ...argsOut,
2003
+ [ Opcodes.local_get, funcLocal ],
2004
+ [ Opcodes.call_indirect, argc, 0, 'no_type_return' ]
2005
+ ]),
2006
+ checkFlag(0b10, [ // type return & constr
2007
+ [ Opcodes.i32_const, decl._new ? 1 : 0 ],
2008
+ ...argsOut,
2009
+ [ Opcodes.local_get, funcLocal ],
2010
+ [ Opcodes.call_indirect, argc, 0, 'constr' ],
2011
+ ...setLastType(scope),
2012
+ ], [
2013
+ ...argsOut,
2014
+ [ Opcodes.local_get, funcLocal ],
2015
+ [ Opcodes.call_indirect, argc, 0 ],
2016
+ ...setLastType(scope),
2017
+ ]),
2018
+ );
1919
2019
  };
1920
2020
 
1921
2021
  const tableBc = {};
@@ -1933,13 +2033,32 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1933
2033
  Opcodes.i32_to_u,
1934
2034
  [ Opcodes.local_set, funcLocal ],
1935
2035
 
2036
+ // get if func we are calling is a constructor or not
2037
+ [ Opcodes.local_get, funcLocal ],
2038
+ ...number(3, Valtype.i32),
2039
+ [ Opcodes.i32_mul ],
2040
+ ...number(2, Valtype.i32),
2041
+ [ Opcodes.i32_add ],
2042
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(allocPage(scope, 'func lut') * pageSize), 'read func lut' ],
2043
+ [ Opcodes.local_set, flags ],
2044
+
2045
+ // check if non-constructor was called with new, if so throw
2046
+ [ Opcodes.local_get, flags ],
2047
+ ...number(0b10, Valtype.i32),
2048
+ [ Opcodes.i32_and ],
2049
+ [ Opcodes.i32_eqz ],
2050
+ [ Opcodes.i32_const, decl._new ? 1 : 0 ],
2051
+ [ Opcodes.i32_and ],
2052
+ [ Opcodes.if, Blocktype.void ],
2053
+ ...internalThrow(scope, 'TypeError', `${unhackName(name)} is not a constructor`),
2054
+ [ Opcodes.end ],
2055
+
1936
2056
  ...brTable([
1937
2057
  // get argc of func we are calling
1938
2058
  [ Opcodes.local_get, funcLocal ],
1939
- ...number(ValtypeSize.i16, Valtype.i32),
2059
+ ...number(3, Valtype.i32),
1940
2060
  [ Opcodes.i32_mul ],
1941
-
1942
- [ Opcodes.i32_load16_u, 0, ...unsignedLEB128(allocPage(scope, 'func argc lut') * pageSize), 'read_argc' ]
2061
+ [ Opcodes.i32_load16_u, 0, ...unsignedLEB128(allocPage(scope, 'func lut') * pageSize), 'read func lut' ]
1943
2062
  ], tableBc, valtypeBinary)
1944
2063
  ],
1945
2064
  default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
@@ -1953,14 +2072,35 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1953
2072
  const userFunc = func && !func.internal;
1954
2073
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1955
2074
  const typedReturns = (userFunc && func.returnType == null) || builtinFuncs[name]?.typedReturns;
1956
- const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1957
-
1958
- let args = decl.arguments;
1959
- if (func && args.length < paramCount) {
2075
+ let paramCount = func && (typedParams ? Math.floor(func.params.length / 2) : func.params.length);
2076
+
2077
+ let paramOffset = 0;
2078
+ if (func && func.constr) {
2079
+ // new.target arg
2080
+ if (func.internal) paramOffset = 1;
2081
+ if (!typedParams) paramCount--;
2082
+ out.push([ Opcodes.i32_const, decl._new ? 1 : 0 ]);
2083
+ } else if (decl._new)
2084
+ return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a constructor`, true);
2085
+
2086
+ let args = [...decl.arguments];
2087
+ if (func && !func.hasRestArgument && args.length < paramCount) {
1960
2088
  // too little args, push undefineds
1961
2089
  args = args.concat(new Array(paramCount - args.length).fill(DEFAULT_VALUE));
1962
2090
  }
1963
2091
 
2092
+ if (func && func.hasRestArgument) {
2093
+ if (args.length < paramCount) {
2094
+ args = args.concat(new Array(paramCount - 1 - args.length).fill(DEFAULT_VALUE));
2095
+ }
2096
+ const restArgs = args.slice(paramCount - 1);
2097
+ args = args.slice(0, paramCount - 1);
2098
+ args.push({
2099
+ type: 'ArrayExpression',
2100
+ elements: restArgs
2101
+ })
2102
+ }
2103
+
1964
2104
  if (func && args.length > paramCount) {
1965
2105
  // too many args, slice extras off
1966
2106
  args = args.slice(0, paramCount);
@@ -1968,7 +2108,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1968
2108
 
1969
2109
  if (func && func.throws) scope.throws = true;
1970
2110
 
1971
- let out = [];
1972
2111
  for (let i = 0; i < args.length; i++) {
1973
2112
  const arg = args[i];
1974
2113
  out = out.concat(generate(scope, arg));
@@ -1981,13 +2120,13 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1981
2120
  }
1982
2121
 
1983
2122
  if (valtypeBinary !== Valtype.i32 &&
1984
- (func && func.params[i * (typedParams ? 2 : 1)] === Valtype.i32)
2123
+ (func && func.params[paramOffset + i * (typedParams ? 2 : 1)] === Valtype.i32)
1985
2124
  ) {
1986
2125
  out.push(Opcodes.i32_to);
1987
2126
  }
1988
2127
 
1989
2128
  if (valtypeBinary === Valtype.i32 &&
1990
- (func && func.params[i * (typedParams ? 2 : 1)] === Valtype.f64)
2129
+ (func && func.params[paramOffset + i * (typedParams ? 2 : 1)] === Valtype.f64)
1991
2130
  ) {
1992
2131
  out.push([ Opcodes.f64_convert_i32_s ]);
1993
2132
  }
@@ -2020,32 +2159,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
2020
2159
  return out;
2021
2160
  };
2022
2161
 
2023
- const generateNew = (scope, decl, _global, _name) => {
2024
- // hack: basically treat this as a normal call for builtins for now
2025
- const name = mapName(decl.callee.name);
2026
-
2027
- if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
2028
-
2029
- if (builtinFuncs[name + '$constructor']) {
2030
- // custom ...$constructor override builtin func
2031
- return generateCall(scope, {
2032
- ...decl,
2033
- callee: {
2034
- type: 'Identifier',
2035
- name: name + '$constructor'
2036
- }
2037
- }, _global, _name);
2038
- }
2039
-
2040
- if (
2041
- (builtinFuncs[name] && !builtinFuncs[name].constr) ||
2042
- (internalConstrs[name] && builtinFuncs[name].notConstr)
2043
- ) return internalThrow(scope, 'TypeError', `${name} is not a constructor`, true);
2044
-
2045
- if (!builtinFuncs[name]) return todo(scope, `new statement is not supported yet`, true); // return todo(scope, `new statement is not supported yet (new ${unhackName(name)})`);
2046
-
2047
- return generateCall(scope, decl, _global, _name);
2048
- };
2162
+ const generateNew = (scope, decl, _global, _name) => generateCall(scope, {
2163
+ ...decl,
2164
+ _new: true
2165
+ }, _global, _name);
2049
2166
 
2050
2167
  // bad hack for undefined and null working without additional logic
2051
2168
  const DEFAULT_VALUE = {
@@ -2053,6 +2170,16 @@ const DEFAULT_VALUE = {
2053
2170
  name: 'undefined'
2054
2171
  };
2055
2172
 
2173
+ const codeToSanitizedStr = code => {
2174
+ let out = '';
2175
+ while (code > 0) {
2176
+ out += String.fromCharCode(97 + code % 26);
2177
+ code -= 26;
2178
+ }
2179
+ return out;
2180
+ };
2181
+ const sanitize = str => str.replace(/[^0-9a-zA-Z_]/g, _ => codeToSanitizedStr(_.charCodeAt(0)));
2182
+
2056
2183
  const unhackName = name => {
2057
2184
  if (name.startsWith('__')) return name.slice(2).replaceAll('_', '.');
2058
2185
  return name;
@@ -2288,9 +2415,120 @@ const generateVar = (scope, decl) => {
2288
2415
  const global = topLevel || decl._bare;
2289
2416
 
2290
2417
  for (const x of decl.declarations) {
2291
- const name = mapName(x.id.name);
2418
+ if (x.id.type === 'ArrayPattern') {
2419
+ const decls = [];
2420
+ const tmpName = '#destructure' + randId();
2421
+
2422
+ let i = 0;
2423
+ const elements = [...x.id.elements];
2424
+ for (const e of elements) {
2425
+ switch (e?.type) {
2426
+ case 'RestElement': { // let [ ...foo ] = []
2427
+ if (e.argument.type === 'ArrayPattern') {
2428
+ // let [ ...[a, b, c] ] = []
2429
+ elements.push(...e.argument.elements);
2430
+ } else {
2431
+ decls.push({
2432
+ type: 'VariableDeclarator',
2433
+ id: { type: 'Identifier', name: e.argument.name },
2434
+ init: {
2435
+ type: 'CallExpression',
2436
+ callee: {
2437
+ type: 'Identifier',
2438
+ name: '__Array_prototype_slice'
2439
+ },
2440
+ arguments: [
2441
+ { type: 'Identifier', name: tmpName },
2442
+ { type: 'Literal', value: i },
2443
+ {
2444
+ type: 'MemberExpression',
2445
+ object: { type: 'Identifier', name: tmpName, },
2446
+ property: { type: 'Identifier', name: 'length', }
2447
+ }
2448
+ ]
2449
+ }
2450
+ });
2451
+ }
2292
2452
 
2293
- if (!name) return todo(scope, 'destructuring is not supported yet');
2453
+ continue; // skip i++
2454
+ }
2455
+
2456
+ case 'Identifier': { // let [ foo ] = []
2457
+ decls.push({
2458
+ type: 'VariableDeclarator',
2459
+ id: e,
2460
+ init: {
2461
+ type: 'MemberExpression',
2462
+ object: { type: 'Identifier', name: tmpName },
2463
+ property: { type: 'Literal', value: i }
2464
+ }
2465
+ });
2466
+
2467
+ break;
2468
+ }
2469
+
2470
+ case 'AssignmentPattern': { // let [ foo = defaultValue ] = []
2471
+ decls.push({
2472
+ type: 'VariableDeclarator',
2473
+ id: e.left,
2474
+ init: {
2475
+ type: 'LogicalExpression',
2476
+ operator: '??',
2477
+ left: {
2478
+ type: 'MemberExpression',
2479
+ object: { type: 'Identifier', name: tmpName },
2480
+ property: { type: 'Literal', value: i }
2481
+ },
2482
+ right: e.right
2483
+ }
2484
+ });
2485
+
2486
+ break;
2487
+ }
2488
+
2489
+ case 'ArrayPattern': { // let [ [ foo, bar ] ] = []
2490
+ decls.push({
2491
+ type: 'VariableDeclarator',
2492
+ id: e,
2493
+ init: {
2494
+ type: 'MemberExpression',
2495
+ object: { type: 'Identifier', name: tmpName },
2496
+ property: { type: 'Literal', value: i }
2497
+ }
2498
+ });
2499
+
2500
+ break;
2501
+ }
2502
+
2503
+ case 'ObjectPattern':
2504
+ return todo(scope, 'object destructuring is not supported yet')
2505
+ }
2506
+
2507
+ i++;
2508
+ }
2509
+
2510
+ out = out.concat([
2511
+ ...generateVar(scope, {
2512
+ type: 'VariableDeclaration',
2513
+ declarations: [{
2514
+ type: 'VariableDeclarator',
2515
+ id: { type: 'Identifier', name: tmpName },
2516
+ init: x.init
2517
+ }],
2518
+ kind: decl.kind
2519
+ }),
2520
+ ...generateVar(scope, {
2521
+ type: 'VariableDeclaration',
2522
+ declarations: decls,
2523
+ kind: decl.kind
2524
+ })
2525
+ ]);
2526
+
2527
+ continue;
2528
+ }
2529
+
2530
+ const name = mapName(x.id.name);
2531
+ if (!name) return todo(scope, 'object destructuring is not supported yet')
2294
2532
 
2295
2533
  if (x.init && isFuncType(x.init.type)) {
2296
2534
  // hack for let a = function () { ... }
@@ -2325,7 +2563,9 @@ const generateVar = (scope, decl) => {
2325
2563
  // hack to set local as pointer before
2326
2564
  out.push(...number(scope.arrays.get(name)), [ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
2327
2565
  if (generated.at(-1) == Opcodes.i32_from_u) generated.pop();
2328
- generated.pop();
2566
+ // generated.pop();
2567
+ generated.push([ Opcodes.drop ]);
2568
+
2329
2569
  out = out.concat(generated);
2330
2570
  } else {
2331
2571
  out = out.concat(generated);
@@ -2392,8 +2632,8 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2392
2632
 
2393
2633
  // arr[i]
2394
2634
  if (decl.left.type === 'MemberExpression' && decl.left.computed) {
2395
- const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
2396
- const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
2635
+ const newValueTmp = localTmp(scope, '#member_setter_val_tmp');
2636
+ const pointerTmp = localTmp(scope, '#member_setter_ptr_tmp', Valtype.i32);
2397
2637
 
2398
2638
  return [
2399
2639
  ...typeSwitch(scope, getNodeType(scope, decl.left.object), {
@@ -2405,11 +2645,11 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2405
2645
  ...generate(scope, decl.left.property),
2406
2646
  Opcodes.i32_to_u,
2407
2647
 
2408
- // turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2648
+ // turn into byte offset by * valtypeSize + 1
2409
2649
  ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2410
2650
  [ Opcodes.i32_mul ],
2411
2651
  [ Opcodes.i32_add ],
2412
- ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2652
+ [ Opcodes.local_tee, pointerTmp ],
2413
2653
 
2414
2654
  ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2415
2655
  [ Opcodes.local_get, pointerTmp ],
@@ -2419,7 +2659,11 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2419
2659
  [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 + ValtypeSize[valtype] ]
2420
2660
  ], getNodeType(scope, decl.right), false, name, true)),
2421
2661
  [ Opcodes.local_tee, newValueTmp ],
2422
- [ Opcodes.store, 0, ValtypeSize.i32 ]
2662
+ [ Opcodes.store, 0, ValtypeSize.i32 ],
2663
+
2664
+ [ Opcodes.local_get, pointerTmp ],
2665
+ ...getNodeType(scope, decl),
2666
+ [ Opcodes.i32_store8, 0, ValtypeSize.i32 + ValtypeSize[valtype] ],
2423
2667
  ],
2424
2668
 
2425
2669
  ...wrapBC({
@@ -3457,6 +3701,22 @@ const generateEmpty = (scope, decl) => {
3457
3701
  return [];
3458
3702
  };
3459
3703
 
3704
+ const generateMeta = (scope, decl) => {
3705
+ if (decl.meta.name !== 'new') return todo(scope, `meta property object ${decl.meta.name} is not supported yet`, true);
3706
+
3707
+ switch (`${decl.meta.name}.${decl.property.name}`) {
3708
+ case 'new.target': {
3709
+ scope.constr = true;
3710
+
3711
+ return [
3712
+ [ Opcodes.local_get, -1 ],
3713
+ Opcodes.i32_from_u,
3714
+ ...setLastType(scope, TYPES.boolean)
3715
+ ];
3716
+ }
3717
+ }
3718
+ };
3719
+
3460
3720
  let pages = new Map();
3461
3721
  const allocPage = (scope, reason, type) => {
3462
3722
  if (pages.has(reason)) return pages.get(reason).ind;
@@ -3809,20 +4069,10 @@ const generateMember = (scope, decl, _global, _name) => {
3809
4069
  const func = funcs.find(x => x.name === name);
3810
4070
  if (func) {
3811
4071
  const typedParams = !func.internal || builtinFuncs[name]?.typedParams;
3812
- return withType(scope, number(typedParams ? func.params.length / 2 : func.params.length), TYPES.number);
3813
- }
3814
-
3815
- if (builtinFuncs[name + '$constructor']) {
3816
- const regularFunc = builtinFuncs[name];
3817
- const regularParams = regularFunc.typedParams ? (regularFunc.params.length / 2) : regularFunc.params.length;
3818
-
3819
- const constructorFunc = builtinFuncs[name + '$constructor'];
3820
- const constructorParams = constructorFunc.typedParams ? (constructorFunc.params.length / 2) : constructorFunc.params.length;
3821
-
3822
- return withType(scope, number(Math.max(regularParams, constructorParams)), TYPES.number);
4072
+ return withType(scope, number(typedParams ? Math.floor(func.params.length / 2) : (func.constr ? (func.params.length - 1) : func.params.length)), TYPES.number);
3823
4073
  }
3824
4074
 
3825
- if (builtinFuncs[name]) return withType(scope, number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length), TYPES.number);
4075
+ if (builtinFuncs[name]) return withType(scope, number(builtinFuncs[name].typedParams ? Math.floor(builtinFuncs[name].params.length / 2) : (builtinFuncs[name].constr ? (builtinFuncs[name].params.length - 1) : builtinFuncs[name].params.length)), TYPES.number);
3826
4076
  if (importedFuncs[name]) return withType(scope, number(importedFuncs[name].params.length ?? importedFuncs[name].params), TYPES.number);
3827
4077
  if (internalConstrs[name]) return withType(scope, number(internalConstrs[name].length ?? 0), TYPES.number);
3828
4078
 
@@ -3871,7 +4121,7 @@ const generateMember = (scope, decl, _global, _name) => {
3871
4121
  }
3872
4122
 
3873
4123
  // todo: generate this array procedurally during builtinFuncs creation
3874
- if (['size', 'description'].includes(decl.property.name)) {
4124
+ if (['size', 'description', 'byteLength'].includes(decl.property.name)) {
3875
4125
  const bc = {};
3876
4126
  const cands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + decl.property.name + '$get'));
3877
4127
 
@@ -4077,7 +4327,7 @@ const objectHack = node => {
4077
4327
  if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
4078
4328
 
4079
4329
  // if .name or .length, give up (hack within a hack!)
4080
- if (['name', 'length', 'size', 'description'].includes(node.property.name)) {
4330
+ if (['name', 'length', 'size', 'description', 'byteLength'].includes(node.property.name)) {
4081
4331
  node.object = objectHack(node.object);
4082
4332
  return;
4083
4333
  }
@@ -4130,8 +4380,8 @@ const generateFunc = (scope, decl) => {
4130
4380
 
4131
4381
  if (typedInput && decl.returnType) {
4132
4382
  const { type } = extractTypeAnnotation(decl.returnType);
4133
- // if (type != null && !Prefs.indirectCalls) {
4134
- if (type != null) {
4383
+ if (type != null && !Prefs.indirectCalls) {
4384
+ // if (type != null) {
4135
4385
  func.returnType = type;
4136
4386
  func.returns = [ valtypeBinary ];
4137
4387
  }
@@ -4152,6 +4402,12 @@ const generateFunc = (scope, decl) => {
4152
4402
  defaultValues[name] = x.right;
4153
4403
  break;
4154
4404
  }
4405
+
4406
+ case 'RestElement': {
4407
+ name = x.argument.name;
4408
+ func.hasRestArgument = true;
4409
+ break;
4410
+ }
4155
4411
  }
4156
4412
 
4157
4413
  // if (name == null) return todo('non-identifier args are not supported');