porffor 0.60.22 → 0.60.24

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.
@@ -3,6 +3,7 @@ import { number, ieee754_binary64, signedLEB128, unsignedLEB128, encodeVector }
3
3
  import { operatorOpcode } from './expression.js';
4
4
  import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from './builtins.js';
5
5
  import { TYPES, TYPE_FLAGS, TYPE_NAMES } from './types.js';
6
+ import semantic from './semantic.js';
6
7
  import parse from './parse.js';
7
8
  import { log } from './log.js';
8
9
  import './prefs.js';
@@ -1198,6 +1199,22 @@ const nullish = (scope, wasm, type, nonbinary = true, intIn = false) => {
1198
1199
  ];
1199
1200
  };
1200
1201
 
1202
+ const eitherStringType = (leftType, rightType) => [
1203
+ ...leftType,
1204
+ number(TYPE_FLAGS.parity, Valtype.i32),
1205
+ [ Opcodes.i32_or ],
1206
+ number(TYPES.bytestring, Valtype.i32),
1207
+ [ Opcodes.i32_eq ],
1208
+
1209
+ ...rightType,
1210
+ number(TYPE_FLAGS.parity, Valtype.i32),
1211
+ [ Opcodes.i32_or ],
1212
+ number(TYPES.bytestring, Valtype.i32),
1213
+ [ Opcodes.i32_eq ],
1214
+
1215
+ [ Opcodes.i32_or ]
1216
+ ];
1217
+
1201
1218
  const performOp = (scope, op, left, right, leftType, rightType) => {
1202
1219
  if (op === '||' || op === '&&' || op === '??') {
1203
1220
  return performLogicOp(scope, op, left, right, leftType, rightType);
@@ -1294,19 +1311,7 @@ const performOp = (scope, op, left, right, leftType, rightType) => {
1294
1311
 
1295
1312
  ops.unshift(
1296
1313
  // if left or right are string or bytestring
1297
- ...leftType,
1298
- number(TYPE_FLAGS.parity, Valtype.i32),
1299
- [ Opcodes.i32_or ],
1300
- number(TYPES.bytestring, Valtype.i32),
1301
- [ Opcodes.i32_eq ],
1302
-
1303
- ...rightType,
1304
- number(TYPE_FLAGS.parity, Valtype.i32),
1305
- [ Opcodes.i32_or ],
1306
- number(TYPES.bytestring, Valtype.i32),
1307
- [ Opcodes.i32_eq ],
1308
-
1309
- [ Opcodes.i32_or ],
1314
+ ...eitherStringType(leftType, rightType),
1310
1315
  [ Opcodes.if, Blocktype.void ],
1311
1316
  ...concatStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], leftType, rightType),
1312
1317
  [ Opcodes.br, 1 ],
@@ -1326,19 +1331,7 @@ const performOp = (scope, op, left, right, leftType, rightType) => {
1326
1331
 
1327
1332
  ops.unshift(
1328
1333
  // if left or right are string or bytestring
1329
- ...leftType,
1330
- number(TYPE_FLAGS.parity, Valtype.i32),
1331
- [ Opcodes.i32_or ],
1332
- number(TYPES.bytestring, Valtype.i32),
1333
- [ Opcodes.i32_eq ],
1334
-
1335
- ...rightType,
1336
- number(TYPE_FLAGS.parity, Valtype.i32),
1337
- [ Opcodes.i32_or ],
1338
- number(TYPES.bytestring, Valtype.i32),
1339
- [ Opcodes.i32_eq ],
1340
-
1341
- [ Opcodes.i32_or ],
1334
+ ...eitherStringType(leftType, rightType),
1342
1335
  [ Opcodes.if, Blocktype.void ],
1343
1336
  ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], leftType, rightType),
1344
1337
  ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
@@ -1599,11 +1592,11 @@ const generateLogicExp = (scope, decl) =>
1599
1592
  const getInferred = (scope, name, global = false) => {
1600
1593
  const isConst = getVarMetadata(scope, name, global)?.kind === 'const';
1601
1594
  if (global) {
1602
- if (globalInfer.has(name) && (isConst || inferLoopPrev.length === 0)) return globalInfer.get(name);
1595
+ if (name in globalInfer && (isConst || inferLoopPrev.length === 0)) return globalInfer[name];
1603
1596
  } else if (scope.inferTree) {
1604
1597
  for (let i = scope.inferTree.length - 1; i >= 0; i--) {
1605
1598
  const x = scope.inferTree[i];
1606
- if (x._infer?.has(name)) return x._infer.get(name);
1599
+ if (name in x) return x[name];
1607
1600
  }
1608
1601
  }
1609
1602
 
@@ -1612,21 +1605,20 @@ const getInferred = (scope, name, global = false) => {
1612
1605
 
1613
1606
  const setInferred = (scope, name, type, global = false) => {
1614
1607
  const isConst = getVarMetadata(scope, name, global)?.kind === 'const';
1615
- scope.inferTree ??= [];
1608
+ scope.inferTree ??= [ Object.create(null) ];
1616
1609
 
1617
1610
  if (global) {
1618
1611
  // set inferred type in global if not already and not in a loop, else make it null
1619
- globalInfer.set(name, globalInfer.has(name) || (!isConst && inferLoopPrev.length > 0) ? null : type);
1612
+ globalInfer[name] = name in globalInfer || (!isConst && inferLoopPrev.length > 0) ? null : type;
1620
1613
  } else {
1621
1614
  // set inferred type in top
1622
1615
  const top = scope.inferTree.at(-1);
1623
- top._infer ??= new Map();
1624
- top._infer.set(name, type);
1616
+ top[name] = type;
1625
1617
 
1626
1618
  // invalidate inferred type above if mismatched
1627
1619
  for (let i = scope.inferTree.length - 2; i >= 0; i--) {
1628
1620
  const x = scope.inferTree[i];
1629
- if (x._infer && x._infer.get(name) !== type) x._infer.set(name, null);
1621
+ if (name in x && x[name] !== type) x[name] = null;
1630
1622
  }
1631
1623
  }
1632
1624
  };
@@ -2051,31 +2043,6 @@ const generateChain = (scope, decl) => {
2051
2043
  return out;
2052
2044
  };
2053
2045
 
2054
- const ArrayUtil = {
2055
- getLengthI32: pointer => [
2056
- ...pointer,
2057
- [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ]
2058
- ],
2059
-
2060
- getLength: pointer => [
2061
- ...pointer,
2062
- [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
2063
- Opcodes.i32_from_u
2064
- ],
2065
-
2066
- setLengthI32: (pointer, value) => [
2067
- ...pointer,
2068
- ...value,
2069
- [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ]
2070
- ],
2071
-
2072
- setLength: (pointer, value) => [
2073
- ...pointer,
2074
- ...value,
2075
- Opcodes.i32_to_u,
2076
- [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ]
2077
- ]
2078
- };
2079
2046
 
2080
2047
  const createNewTarget = (scope, decl, idx = 0, force = false) => {
2081
2048
  if (decl._new || force) {
@@ -2264,7 +2231,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
2264
2231
  try {
2265
2232
  parsed = {
2266
2233
  type: 'BlockStatement',
2267
- body: parse(code).body.map(objectHack)
2234
+ body: semantic(objectHack(parse(code)), decl._semanticScopes).body
2268
2235
  };
2269
2236
  } catch (e) {
2270
2237
  if (e.name === 'SyntaxError') {
@@ -2309,7 +2276,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
2309
2276
 
2310
2277
  let parsed;
2311
2278
  try {
2312
- parsed = objectHack(parse(`(function(${args.join(',')}){${code}})`));
2279
+ parsed = semantic(objectHack(parse(`(function(${args.join(',')}){${code}})`)), decl._semanticScopes);
2313
2280
  } catch (e) {
2314
2281
  if (e.name === 'SyntaxError') {
2315
2282
  // throw syntax errors of evals at runtime instead
@@ -2824,16 +2791,6 @@ const DEFAULT_VALUE = () => ({
2824
2791
  name: 'undefined'
2825
2792
  });
2826
2793
 
2827
- const codeToSanitizedStr = code => {
2828
- let out = '';
2829
- while (code > 0) {
2830
- out += String.fromCharCode(97 + code % 26);
2831
- code -= 26;
2832
- }
2833
- return out;
2834
- };
2835
- const sanitize = str => str.replace(/[^0-9a-zA-Z_]/g, _ => codeToSanitizedStr(_.charCodeAt(0)));
2836
-
2837
2794
  const unhackName = name => {
2838
2795
  if (!name) return name;
2839
2796
 
@@ -3284,6 +3241,7 @@ const setDefaultFuncName = (decl, name) => {
3284
3241
  }
3285
3242
  }
3286
3243
 
3244
+ name = name.split('#')[0];
3287
3245
  decl.id = { name };
3288
3246
  };
3289
3247
 
@@ -3299,8 +3257,7 @@ const generateVarDstr = (scope, kind, pattern, init, defaultValue, global) => {
3299
3257
  try {
3300
3258
  let usedNames = [];
3301
3259
  for (const x of pattern.properties) {
3302
- const name = x.key.name;
3303
- usedNames.push(name);
3260
+ usedNames.push(x.key.name);
3304
3261
  }
3305
3262
 
3306
3263
  let path = init.arguments[0].value;
@@ -3327,11 +3284,11 @@ const generateVarDstr = (scope, kind, pattern, init, defaultValue, global) => {
3327
3284
 
3328
3285
  // mock ffi function
3329
3286
  asmFunc(name, {
3330
- wasm: [],
3287
+ wasm: () => [],
3331
3288
  params: parameters.map(x => Valtype.i32),
3332
3289
  returns: result ? [ Valtype.i32 ] : [],
3333
3290
  returnType: TYPES.number
3334
- })
3291
+ });
3335
3292
  }
3336
3293
 
3337
3294
  return [ [ null, 'dlopen', path, symbols ] ];
@@ -4523,27 +4480,26 @@ const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
4523
4480
  ];
4524
4481
  };
4525
4482
 
4526
- const inferBranchStart = (scope, decl) => {
4527
- scope.inferTree ??= [];
4528
- scope.inferTree.push(decl);
4483
+ const inferBranchStart = scope => {
4484
+ scope.inferTree ??= [ Object.create(null) ];
4485
+ scope.inferTree.push(Object.create(null));
4529
4486
  };
4530
4487
 
4531
4488
  const inferBranchEnd = scope => {
4532
4489
  scope.inferTree.pop();
4533
4490
  };
4534
4491
 
4535
- const inferBranchElse = (scope, decl) => {
4492
+ const inferBranchElse = scope => {
4493
+ // todo/opt: at end of else, find inferences in common and keep them?
4536
4494
  inferBranchEnd(scope);
4537
- inferBranchStart(scope, decl);
4495
+ inferBranchStart(scope);
4538
4496
  };
4539
4497
 
4540
4498
  const inferLoopPrev = [];
4541
- const inferLoopStart = (scope, decl) => {
4542
- scope.inferTree ??= [];
4543
-
4499
+ const inferLoopStart = scope => {
4544
4500
  // todo/opt: do not just wipe the infer tree for loops
4545
- inferLoopPrev.push(scope.inferTree);
4546
- scope.inferTree = [ decl ];
4501
+ inferLoopPrev.push(scope.inferTree ?? [ Object.create(null) ]);
4502
+ scope.inferTree = [ Object.create(null) ];
4547
4503
  };
4548
4504
 
4549
4505
  const inferLoopEnd = scope => {
@@ -4562,23 +4518,23 @@ const generateIf = (scope, decl) => {
4562
4518
  const out = truthy(scope, generate(scope, decl.test), getNodeType(scope, decl.test));
4563
4519
  out.push([ Opcodes.if, Blocktype.void ]);
4564
4520
  depth.push('if');
4565
- inferBranchStart(scope, decl.consequent);
4521
+ inferBranchStart(scope);
4566
4522
 
4567
4523
  out.push(
4568
4524
  ...generate(scope, decl.consequent),
4569
4525
  [ Opcodes.drop ]
4570
4526
  );
4571
4527
 
4572
- inferBranchEnd(scope);
4528
+
4573
4529
  if (decl.alternate) {
4574
- inferBranchStart(scope, decl.alternate);
4530
+ inferBranchElse(scope);
4575
4531
  out.push(
4576
4532
  [ Opcodes.else ],
4577
4533
  ...generate(scope, decl.alternate),
4578
4534
  [ Opcodes.drop ]
4579
4535
  );
4580
4536
  inferBranchEnd(scope);
4581
- }
4537
+ } else inferBranchEnd(scope);
4582
4538
 
4583
4539
  out.push(
4584
4540
  [ Opcodes.end ],
@@ -4594,7 +4550,7 @@ const generateConditional = (scope, decl) => {
4594
4550
 
4595
4551
  out.push([ Opcodes.if, valtypeBinary ]);
4596
4552
  depth.push('if');
4597
- inferBranchStart(scope, decl.consequent);
4553
+ inferBranchStart(scope);
4598
4554
 
4599
4555
  out.push(
4600
4556
  ...generate(scope, decl.consequent),
@@ -4602,7 +4558,7 @@ const generateConditional = (scope, decl) => {
4602
4558
  );
4603
4559
 
4604
4560
  out.push([ Opcodes.else ]);
4605
- inferBranchElse(scope, decl.alternate);
4561
+ inferBranchElse(scope);
4606
4562
 
4607
4563
  out.push(
4608
4564
  ...generate(scope, decl.alternate),
@@ -4624,7 +4580,7 @@ const generateFor = (scope, decl) => {
4624
4580
  [ Opcodes.drop ]
4625
4581
  );
4626
4582
 
4627
- inferLoopStart(scope, decl);
4583
+ inferLoopStart(scope);
4628
4584
  out.push([ Opcodes.loop, Blocktype.void ]);
4629
4585
  depth.push('for');
4630
4586
 
@@ -4663,7 +4619,7 @@ const generateFor = (scope, decl) => {
4663
4619
 
4664
4620
  const generateWhile = (scope, decl) => {
4665
4621
  const out = [];
4666
- inferLoopStart(scope, decl);
4622
+ inferLoopStart(scope);
4667
4623
 
4668
4624
  out.push([ Opcodes.loop, Blocktype.void ]);
4669
4625
  depth.push('while');
@@ -4691,7 +4647,7 @@ const generateWhile = (scope, decl) => {
4691
4647
 
4692
4648
  const generateDoWhile = (scope, decl) => {
4693
4649
  const out = [];
4694
- inferLoopStart(scope, decl);
4650
+ inferLoopStart(scope);
4695
4651
 
4696
4652
  out.push([ Opcodes.loop, Blocktype.void ]);
4697
4653
 
@@ -4764,7 +4720,7 @@ const generateForOf = (scope, decl) => {
4764
4720
  [ Opcodes.local_set, length ]
4765
4721
  );
4766
4722
 
4767
- inferLoopStart(scope, decl);
4723
+ inferLoopStart(scope);
4768
4724
  depth.push('forof');
4769
4725
  depth.push('block');
4770
4726
 
@@ -5104,7 +5060,7 @@ const generateForIn = (scope, decl) => {
5104
5060
  [ Opcodes.if, Blocktype.void ]
5105
5061
  );
5106
5062
 
5107
- inferLoopStart(scope, decl);
5063
+ inferLoopStart(scope);
5108
5064
  depth.push('if');
5109
5065
  depth.push('forin');
5110
5066
  depth.push('block');
@@ -6769,9 +6725,13 @@ const objectHack = node => {
6769
6725
  }
6770
6726
 
6771
6727
  for (const x in node) {
6772
- if (node[x] != null && typeof node[x] === 'object') {
6728
+ if (node[x] != null && typeof node[x] === 'object' && x[0] !== '_') {
6773
6729
  if (node[x].type) node[x] = objectHack(node[x]);
6774
- if (Array.isArray(node[x])) node[x] = node[x].map(objectHack);
6730
+ if (Array.isArray(node[x])) {
6731
+ for (let i = 0; i < node[x].length; i++) {
6732
+ node[x][i] = objectHack(node[x][i]);
6733
+ }
6734
+ }
6775
6735
  }
6776
6736
  }
6777
6737
 
@@ -6829,7 +6789,6 @@ const generateFunc = (scope, decl, forceNoExpr = false) => {
6829
6789
  async: decl.async,
6830
6790
  subclass: decl._subclass, _onlyConstr: decl._onlyConstr, _onlyThisMethod: decl._onlyThisMethod,
6831
6791
  strict: scope.strict || decl.strict,
6832
- inferTree: [ decl ],
6833
6792
 
6834
6793
  generate() {
6835
6794
  if (func.wasm) return func.wasm;
@@ -6837,7 +6796,7 @@ const generateFunc = (scope, decl, forceNoExpr = false) => {
6837
6796
  // generating, stub _wasm
6838
6797
  let wasm = func.wasm = [];
6839
6798
 
6840
- let body = objectHack(decl.body);
6799
+ let body = decl.body;
6841
6800
  if (decl.type === 'ArrowFunctionExpression' && decl.expression) {
6842
6801
  // hack: () => 0 -> () => return 0
6843
6802
  body = {
@@ -7185,8 +7144,7 @@ const generateFunc = (scope, decl, forceNoExpr = false) => {
7185
7144
  const generateBlock = (scope, decl) => {
7186
7145
  let out = [];
7187
7146
 
7188
- scope.inferTree ??= [];
7189
- scope.inferTree.push(decl);
7147
+ inferBranchStart(scope);
7190
7148
 
7191
7149
  let len = decl.body.length, j = 0;
7192
7150
  for (let i = 0; i < len; i++) {
@@ -7197,7 +7155,7 @@ const generateBlock = (scope, decl) => {
7197
7155
  out = out.concat(generate(scope, x));
7198
7156
  }
7199
7157
 
7200
- scope.inferTree.pop();
7158
+ inferBranchEnd(scope);
7201
7159
 
7202
7160
  if (out.length === 0) out.push(number(UNDEFINED));
7203
7161
  return out;
@@ -7403,7 +7361,7 @@ export default program => {
7403
7361
  typeswitchDepth = 0;
7404
7362
  usedTypes = new Set([ TYPES.undefined, TYPES.number, TYPES.boolean, TYPES.function ]);
7405
7363
  coctc = new Map();
7406
- globalInfer = new Map();
7364
+ globalInfer = Object.create(null);
7407
7365
 
7408
7366
  // set generic opcodes for current valtype
7409
7367
  Opcodes.const = valtypeBinary === Valtype.i32 ? Opcodes.i32_const : Opcodes.f64_const;
@@ -7429,7 +7387,11 @@ export default program => {
7429
7387
  objectHackers = ['assert', 'compareArray', 'Test262Error', ...new Set(Object.keys(builtinFuncs).map(getObjectName).concat(Object.keys(builtinVars).map(getObjectName)).filter(x => x))];
7430
7388
  }
7431
7389
 
7432
- const [ main ] = generateFunc({}, {
7390
+ // todo/perf: make this lazy per func (again)
7391
+ program = objectHack(program);
7392
+ if (Prefs.closures) program = semantic(program);
7393
+
7394
+ generateFunc({}, {
7433
7395
  type: 'Program',
7434
7396
  id: { name: '#main' },
7435
7397
  body: {
@@ -7438,9 +7400,6 @@ export default program => {
7438
7400
  }
7439
7401
  });
7440
7402
 
7441
- // if wanted and blank main func and other exports, remove it
7442
- if (Prefs.rmBlankMain && main.wasm.length === 0 && funcs.some(x => x.export)) funcs.splice(main.index - importedFuncs.length, 1);
7443
-
7444
7403
  for (let i = 0; i < funcs.length; i++) {
7445
7404
  const f = funcs[i];
7446
7405
 
package/compiler/parse.js CHANGED
@@ -17,7 +17,7 @@ globalThis.parser = '';
17
17
  let parse;
18
18
  const loadParser = async (fallbackParser = 'acorn', forceParser) => {
19
19
  parser = forceParser ?? Prefs.parser ?? fallbackParser;
20
- const mod = (await import((globalThis.document || globalThis.Deno ? 'https://esm.sh/' : '') + parser));
20
+ const mod = (await import((globalThis.document ? 'https://esm.sh/' : '') + parser));
21
21
  if (mod.parseSync) parse = mod.parseSync;
22
22
  else parse = mod.parse;
23
23
  };
@@ -57,7 +57,7 @@ const compile = async (file, _funcs) => {
57
57
  first = source.slice(0, source.indexOf('\n'));
58
58
  }
59
59
 
60
- let args = ['--module', '--truthy=no_nan_negative', '--no-rm-unused-types', '--fast-length', '--parse-types', '--opt-types', '--no-passive-data', '--active-data', '--no-treeshake-wasm-imports', '--no-coctc'];
60
+ let args = ['--module', '--truthy=no_nan_negative', '--no-rm-unused-types', '--fast-length', '--parse-types', '--opt-types', '--no-passive-data', '--active-data', '--no-treeshake-wasm-imports', '--no-coctc', '--no-closures'];
61
61
  if (first.startsWith('// @porf')) {
62
62
  args = first.slice('// @porf '.length).split(' ').concat(args);
63
63
  }
package/compiler/prefs.js CHANGED
@@ -1,4 +1,4 @@
1
- const onByDefault = [ 'treeshakeWasmImports', 'alwaysMemory', 'indirectCalls', 'optUnused', 'data', 'passiveData', 'rmUnusedTypes', 'optTypes', 'coctc', 'ctHash' ];
1
+ const onByDefault = [ 'treeshakeWasmImports', 'alwaysMemory', 'indirectCalls', 'optUnused', 'data', 'passiveData', 'rmUnusedTypes', 'optTypes', 'coctc', 'ctHash', 'closures' ];
2
2
 
3
3
  const nameToKey = x => x.replace(/[a-z]\-[a-z]/g, y => `${y[0]}${y[2].toUpperCase()}`);
4
4
 
@@ -0,0 +1,184 @@
1
+ // todo: sloppy vs strict mode
2
+ // todo: function/class decls ?
3
+ // todo: function params
4
+
5
+ const varId = name => {
6
+ const lastFunc = scopes[scopes.lastFuncs.at(-1)];
7
+ lastFunc._variableIds ??= Object.create(null);
8
+ lastFunc._variableIds[name] ??= 0;
9
+ return lastFunc._variableIds[name]++;
10
+
11
+ // scopes._varId ??= Object.create(null);
12
+ // scopes._varId[name] ??= 0;
13
+ // return scopes._varId[name]++;
14
+ };
15
+
16
+ const declVar = (name, kind, node) => {
17
+ const parent = kind === 'var' ? scopes[scopes.lastFuncs.at(-1)] : scopes.at(-1);
18
+ parent._variables ??= Object.create(null);
19
+ parent._variables[name] = { node, id: varId(name) };
20
+ };
21
+
22
+ const analyzePattern = (kind, node) => {
23
+ if (!node) return;
24
+ switch (node.type) {
25
+ case 'Identifier':
26
+ declVar(node.name, kind, node);
27
+ break;
28
+
29
+ case 'RestElement':
30
+ analyzePattern(kind, node.argument);
31
+ break;
32
+
33
+ case 'AssignmentPattern':
34
+ analyzePattern(kind, node.left);
35
+ break;
36
+
37
+ case 'Property':
38
+ analyzePattern(kind, node.value);
39
+ break;
40
+
41
+ case 'ObjectPattern':
42
+ for (const x of node.properties) {
43
+ analyzePattern(kind, x.value);
44
+ }
45
+ break;
46
+
47
+ case 'ArrayPattern':
48
+ for (const x of node.elements) {
49
+ analyzePattern(kind, x);
50
+ }
51
+ break;
52
+ }
53
+ };
54
+
55
+ let scopes;
56
+ const analyze = node => {
57
+ if (!node) return;
58
+
59
+ let openedScope = false;
60
+ switch (node.type) {
61
+ case 'ForStatement':
62
+ case 'ForInStatement':
63
+ case 'ForOfStatement':
64
+ case 'SwitchStatement':
65
+ case 'BlockStatement':
66
+ scopes.push(node);
67
+ openedScope = true;
68
+ break;
69
+
70
+ case 'CatchClause':
71
+ scopes.push(node);
72
+ if (node.param) analyzePattern('let', node.param);
73
+ openedScope = true;
74
+ break;
75
+
76
+ case 'VariableDeclaration':
77
+ for (const x of node.declarations) analyzePattern(node.kind, x.id);
78
+ break;
79
+
80
+ case 'FunctionDeclaration':
81
+ case 'FunctionExpression':
82
+ case 'ArrowFunctionExpression':
83
+ scopes.lastFuncs.push(scopes.length);
84
+ break;
85
+ }
86
+
87
+ for (const x in node) {
88
+ if (node[x] != null && typeof node[x] === 'object') {
89
+ if (node[x].type) analyze(node[x]);
90
+ if (Array.isArray(node[x])) {
91
+ for (const y of node[x]) analyze(y);
92
+ }
93
+ }
94
+ }
95
+
96
+ if (openedScope) {
97
+ scopes.pop();
98
+ }
99
+
100
+ if (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') {
101
+ scopes.lastFuncs.pop();
102
+ }
103
+ };
104
+
105
+ const objectHackers = ["assert","compareArray","Test262Error","Number","Math","Porffor","performance","String","ByteString","Array","ArrayBuffer","SharedArrayBuffer","Atomics","ecma262","BigInt","Boolean","console","crypto","DataView","Date","Error","AggregateError","TypeError","ReferenceError","SyntaxError","RangeError","EvalError","URIError","Test262Error","Function","JSON","Map","Object","Promise","Reflect","RegExp","Set","Symbol","Uint8Array","Int8Array","Uint8ClampedArray","Uint16Array","Int16Array","Uint32Array","Int32Array","Float32Array","Float64Array","BigInt64Array","BigUint64Array","WeakMap","WeakRef","WeakSet","navigator"];
106
+ const annotate = node => {
107
+ if (!node) return;
108
+
109
+ let openedScope = false;
110
+ if (node._variables) {
111
+ scopes.push(node);
112
+ openedScope = true;
113
+ }
114
+
115
+ switch (node.type) {
116
+ case 'Identifier':
117
+ if (objectHackers.includes(node.name)) break;
118
+ for (let i = scopes.length - 1; i >= 0; i--) {
119
+ if (scopes[i]._variables?.[node.name]) {
120
+ const variable = scopes[i]._variables[node.name];
121
+ if (variable.id > 0) node.name = node.name + '#' + variable.id;
122
+ break;
123
+ }
124
+ }
125
+ break;
126
+
127
+ case 'MemberExpression':
128
+ if (node.computed) annotate(node.property);
129
+ annotate(node.object);
130
+ return;
131
+
132
+ case 'PropertyDefinition':
133
+ case 'Property':
134
+ if (node.computed) annotate(node.key);
135
+ annotate(node.value);
136
+ return;
137
+
138
+ case 'CallExpression':
139
+ if (node.callee.name === 'eval' || (node.callee.type === 'SequenceExpression' && node.callee.expressions.at(-1)?.name === 'eval')) {
140
+ if (node.callee.type === 'SequenceExpression' || node.optional) {
141
+ // indirect eval, no scopes
142
+ node._semanticScopes = [ node ];
143
+ node._semanticScopes.lastFuncs = [ 0 ];
144
+ } else {
145
+ // direct eval, use existing scope
146
+ node._semanticScopes = Object.assign([], scopes);
147
+ }
148
+ }
149
+
150
+ case 'NewExpression':
151
+ if (node.callee.name === 'Function') {
152
+ // todo: this is probably wrong and needs to add own new scope
153
+ node._semanticScopes = Object.assign([], scopes);
154
+ }
155
+ break;
156
+ }
157
+
158
+ for (const x in node) {
159
+ if (node[x] != null && typeof node[x] === 'object' && x[0] !== '_') {
160
+ if (node[x].type) annotate(node[x]);
161
+ if (Array.isArray(node[x])) {
162
+ for (const y of node[x]) annotate(y);
163
+ }
164
+ }
165
+ }
166
+
167
+ if (openedScope) {
168
+ scopes.pop();
169
+ }
170
+ };
171
+
172
+ export default (node, _scopes = null) => {
173
+ if (!_scopes) {
174
+ _scopes = [ node ];
175
+ _scopes.lastFuncs = [ 0 ];
176
+ }
177
+ scopes = _scopes;
178
+
179
+ analyze(node);
180
+ if (scopes.length !== _scopes.length) throw new Error('Scope mismatch');
181
+
182
+ annotate(node);
183
+ return node;
184
+ };
package/jsr.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@honk/porffor",
3
- "version": "0.60.22",
3
+ "version": "0.60.24",
4
4
  "exports": "./compiler/wrap.js",
5
5
  "publish": {
6
6
  "exclude": [
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "porffor",
3
3
  "description": "An ahead-of-time JavaScript compiler",
4
- "version": "0.60.22",
4
+ "version": "0.60.24",
5
5
  "author": "Oliver Medhurst <honk@goose.icu>",
6
6
  "license": "MIT",
7
7
  "scripts": {},
package/runtime/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import fs from 'node:fs';
3
- globalThis.version = '0.60.22';
3
+ globalThis.version = '0.60.24';
4
4
 
5
5
  // deno compat
6
6
  if (typeof process === 'undefined' && typeof Deno !== 'undefined') {
package/foo.js DELETED
@@ -1 +0,0 @@
1
- (typeof console === 'undefined' ? print : console.log)('lol');