storm-lua-minify 0.3.0 → 0.9.0

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.
Files changed (43) hide show
  1. package/README.md +119 -41
  2. package/dist/aggregateSpecialization.js +406 -0
  3. package/dist/ast2lua.js +156 -68
  4. package/dist/astWalk.js +162 -0
  5. package/dist/callGraph.js +372 -0
  6. package/dist/cli.js +53 -58
  7. package/dist/cliOptions.js +36 -0
  8. package/dist/cliProgress.js +87 -0
  9. package/dist/config.js +73 -0
  10. package/dist/constantFold.js +798 -0
  11. package/dist/controlFlow.js +266 -0
  12. package/dist/functionRewrites.js +580 -0
  13. package/dist/generatedAst.js +108 -0
  14. package/dist/generatedNode.js +23 -0
  15. package/dist/interproceduralAnalysis.js +842 -0
  16. package/dist/interproceduralConstants.js +120 -0
  17. package/dist/luaString.js +157 -0
  18. package/dist/minifier.js +1178 -44
  19. package/dist/optimizerAnalysis.js +43 -0
  20. package/dist/optimizerDiagnostics.js +65 -0
  21. package/dist/optimizerFacts.js +529 -0
  22. package/dist/optimizerPass.js +96 -0
  23. package/dist/optimizerTransaction.js +56 -0
  24. package/dist/optimizerValueDomain.js +180 -0
  25. package/dist/options.js +233 -0
  26. package/dist/progress.js +2 -0
  27. package/dist/removeUnused.js +145 -0
  28. package/dist/renamer.js +223 -54
  29. package/dist/resolver.js +28 -11
  30. package/dist/runtimeEnvironment.js +105 -0
  31. package/dist/sourceMetadata.js +314 -0
  32. package/dist/statementDataflow.js +259 -0
  33. package/dist/statementScheduler.js +595 -0
  34. package/dist/symbolLiveness.js +92 -0
  35. package/dist/tableEffects.js +356 -0
  36. package/dist/transform.js +10 -371
  37. package/dist/valueFlow.js +409 -0
  38. package/dist/wholeProgramExports.js +646 -0
  39. package/dist/wholeProgramFieldRenames.js +583 -0
  40. package/dist/wholeProgramFields.js +672 -0
  41. package/dist/wholeProgramObjects.js +783 -0
  42. package/package.json +11 -2
  43. package/dist/index.js +0 -27
@@ -0,0 +1,580 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.pruneTrailingUnusedParameters = pruneTrailingUnusedParameters;
4
+ exports.inlineClosedSingleUseFunctions = inlineClosedSingleUseFunctions;
5
+ exports.inlineLiteralArgumentFunctions = inlineLiteralArgumentFunctions;
6
+ exports.inlineClosedStatementFunctions = inlineClosedStatementFunctions;
7
+ exports.inlineBoundStatementFunctions = inlineBoundStatementFunctions;
8
+ exports.inlineTailCallFunctions = inlineTailCallFunctions;
9
+ const astWalk_1 = require("./astWalk");
10
+ const generatedNode_1 = require("./generatedNode");
11
+ /**
12
+ * Remove only the trailing, identifier parameters proven unused by Resolve.
13
+ *
14
+ * Call arguments deliberately remain untouched: Lua evaluates surplus actuals in
15
+ * their original order and then discards them, which preserves their effects,
16
+ * errors, and multiple-value adjustment. A vararg tail is therefore a hard
17
+ * boundary rather than something this rewrite tries to reason around.
18
+ */
19
+ function pruneTrailingUnusedParameters(callGraph, metadata, canRewrite = () => true) {
20
+ let prunedParameters = 0;
21
+ let prunedMethodParameters = 0;
22
+ callGraph.functions.forEach((callable) => {
23
+ if (!canRewrite(callable))
24
+ return;
25
+ const declaration = callable.declaration;
26
+ if (metadata.annotationsOf(declaration).keep)
27
+ return;
28
+ let retained = declaration.parameters.length;
29
+ while (retained > 0) {
30
+ const parameter = declaration.parameters[retained - 1];
31
+ if (parameter.type !== "Identifier")
32
+ break;
33
+ const symbol = callable.parameters.find((candidate) => candidate.declaration === parameter);
34
+ if (!symbol || symbol.references.length > 0)
35
+ break;
36
+ retained--;
37
+ }
38
+ if (retained === declaration.parameters.length)
39
+ return;
40
+ const removed = declaration.parameters.length - retained;
41
+ prunedParameters += removed;
42
+ if (declaration.identifier?.type === "MemberExpression" &&
43
+ declaration.identifier.indexer === ":")
44
+ prunedMethodParameters += removed;
45
+ declaration.parameters = declaration.parameters.slice(0, retained);
46
+ });
47
+ return {
48
+ changed: prunedParameters > 0,
49
+ prunedParameters,
50
+ prunedMethodParameters,
51
+ };
52
+ }
53
+ function overwriteExpression(target, replacement) {
54
+ // Visitors dispatch exclusively on `type`; fields from the former node are
55
+ // inert after this assignment and do not need an unsafe dynamic deletion.
56
+ Object.assign(target, replacement);
57
+ }
58
+ function isClosedInlineExpression(expression, resolved) {
59
+ let closed = true;
60
+ (0, astWalk_1.walkExpression)(expression, {
61
+ onIdentifierReference: (identifier) => {
62
+ if (resolved.symbolOf(identifier))
63
+ closed = false;
64
+ },
65
+ onFunction: () => {
66
+ closed = false;
67
+ },
68
+ });
69
+ return closed;
70
+ }
71
+ function primitiveLiteral(expression) {
72
+ if (!expression)
73
+ return { type: "NilLiteral", value: null, raw: "nil" };
74
+ switch (expression.type) {
75
+ case "StringLiteral":
76
+ case "NumericLiteral":
77
+ case "BooleanLiteral":
78
+ case "NilLiteral":
79
+ return expression;
80
+ default:
81
+ return undefined;
82
+ }
83
+ }
84
+ function substituteParameters(expression, replacements) {
85
+ const clone = structuredClone(expression);
86
+ const substitute = (original, copied) => {
87
+ if (original.type !== copied.type)
88
+ throw new Error("Cloned expression changed node type");
89
+ switch (original.type) {
90
+ case "Identifier": {
91
+ const replacement = replacements.get(original.name);
92
+ if (replacement)
93
+ overwriteExpression(copied, structuredClone(replacement));
94
+ return;
95
+ }
96
+ case "StringLiteral":
97
+ case "NumericLiteral":
98
+ case "BooleanLiteral":
99
+ case "NilLiteral":
100
+ case "VarargLiteral":
101
+ return;
102
+ case "LogicalExpression":
103
+ case "BinaryExpression": {
104
+ const binary = copied;
105
+ substitute(original.left, binary.left);
106
+ substitute(original.right, binary.right);
107
+ return;
108
+ }
109
+ case "UnaryExpression":
110
+ substitute(original.argument, copied.argument);
111
+ return;
112
+ case "CallExpression": {
113
+ const call = copied;
114
+ substitute(original.base, call.base);
115
+ original.arguments.forEach((argument, index) => {
116
+ substitute(argument, call.arguments[index]);
117
+ });
118
+ return;
119
+ }
120
+ case "TableCallExpression": {
121
+ const call = copied;
122
+ substitute(original.base, call.base);
123
+ substitute(original.arguments, call.arguments);
124
+ return;
125
+ }
126
+ case "StringCallExpression": {
127
+ const call = copied;
128
+ substitute(original.base, call.base);
129
+ substitute(original.argument, call.argument);
130
+ return;
131
+ }
132
+ case "IndexExpression": {
133
+ const index = copied;
134
+ substitute(original.base, index.base);
135
+ substitute(original.index, index.index);
136
+ return;
137
+ }
138
+ case "MemberExpression":
139
+ substitute(original.base, copied.base);
140
+ return;
141
+ case "TableConstructorExpression": {
142
+ const table = copied;
143
+ original.fields.forEach((field, index) => {
144
+ const copiedField = table.fields[index];
145
+ if (field.type === "TableKey") {
146
+ if (copiedField.type !== "TableKey")
147
+ throw new Error("Cloned table field changed node type");
148
+ substitute(field.key, copiedField.key);
149
+ }
150
+ substitute(field.value, copiedField.value);
151
+ });
152
+ return;
153
+ }
154
+ case "FunctionDeclaration":
155
+ throw new Error("Nested functions are not substitutable");
156
+ }
157
+ };
158
+ substitute(expression, clone);
159
+ return clone;
160
+ }
161
+ function onlyParametersOrGlobals(expression, resolved, parameterNames) {
162
+ let safe = true;
163
+ (0, astWalk_1.walkExpression)(expression, {
164
+ onIdentifierReference: (identifier) => {
165
+ const symbol = resolved.symbolOf(identifier);
166
+ if (symbol && !parameterNames.has(symbol.name))
167
+ safe = false;
168
+ },
169
+ onFunction: () => {
170
+ safe = false;
171
+ },
172
+ });
173
+ return safe;
174
+ }
175
+ /**
176
+ * Inline the first deliberately narrow class whose binding proof is complete:
177
+ * a zero-argument, single-use local function returning one closed expression.
178
+ * Closed means every identifier is global; local/upvalue references wait for
179
+ * symbol-level alpha conversion instead of being guessed from their spelling.
180
+ */
181
+ function inlineClosedSingleUseFunctions(analysis, resolved, metadata) {
182
+ let inlinedFunctions = 0;
183
+ analysis.callGraph.functions.forEach((callable) => {
184
+ const declaration = callable.declaration;
185
+ const symbol = callable.symbol;
186
+ if (!symbol ||
187
+ !declaration.isLocal ||
188
+ declaration.parameters.length !== 0 ||
189
+ declaration.body.length !== 1 ||
190
+ symbol.references.length !== 1 ||
191
+ metadata.annotationsOf(declaration).keep)
192
+ return;
193
+ const returned = declaration.body[0];
194
+ if (returned.type !== "ReturnStatement" || returned.arguments.length !== 1)
195
+ return;
196
+ const expression = returned.arguments[0];
197
+ if (!isClosedInlineExpression(expression, resolved))
198
+ return;
199
+ const site = analysis.callGraph.calls.find((candidate) => !candidate.hasUnknownTarget &&
200
+ candidate.targets.size === 1 &&
201
+ candidate.targets.has(callable) &&
202
+ candidate.call.type === "CallExpression" &&
203
+ candidate.call.arguments.length === 0 &&
204
+ candidate.call.base === symbol.references[0]);
205
+ if (!site)
206
+ return;
207
+ // structuredClone retains loc/range on every copied node, so the inlined
208
+ // expression maps to the function body/return origin rather than call-site text.
209
+ overwriteExpression(site.call, structuredClone(expression));
210
+ inlinedFunctions++;
211
+ });
212
+ return { changed: inlinedFunctions > 0, inlinedFunctions };
213
+ }
214
+ /** Inline a single-return function when every actual is an immutable literal. */
215
+ function inlineLiteralArgumentFunctions(analysis, resolved, metadata) {
216
+ let inlinedFunctions = 0;
217
+ analysis.callGraph.functions.forEach((callable) => {
218
+ const declaration = callable.declaration;
219
+ const symbol = callable.symbol;
220
+ if (!symbol ||
221
+ !declaration.isLocal ||
222
+ declaration.parameters.length === 0 ||
223
+ declaration.parameters.some((parameter) => parameter.type !== "Identifier") ||
224
+ declaration.body.length !== 1 ||
225
+ symbol.references.length !== 1 ||
226
+ metadata.annotationsOf(declaration).keep)
227
+ return;
228
+ const returned = declaration.body[0];
229
+ if (returned.type !== "ReturnStatement" || returned.arguments.length !== 1)
230
+ return;
231
+ const site = analysis.callGraph.calls.find((candidate) => !candidate.hasUnknownTarget &&
232
+ candidate.targets.size === 1 &&
233
+ candidate.targets.has(callable) &&
234
+ candidate.call.type === "CallExpression" &&
235
+ candidate.call.arguments.length <= declaration.parameters.length &&
236
+ candidate.call.base === symbol.references[0]);
237
+ if (!site || site.call.type !== "CallExpression")
238
+ return;
239
+ const replacements = new Map();
240
+ for (let index = 0; index < declaration.parameters.length; index++) {
241
+ const parameter = declaration.parameters[index];
242
+ if (parameter.type !== "Identifier")
243
+ return;
244
+ const actual = primitiveLiteral(site.call.arguments[index]);
245
+ if (!actual)
246
+ return;
247
+ replacements.set(parameter.name, actual);
248
+ }
249
+ if (!onlyParametersOrGlobals(returned.arguments[0], resolved, new Set(replacements.keys())))
250
+ return;
251
+ overwriteExpression(site.call, substituteParameters(returned.arguments[0], replacements));
252
+ inlinedFunctions++;
253
+ });
254
+ return { changed: inlinedFunctions > 0, inlinedFunctions };
255
+ }
256
+ function statementChildren(statement) {
257
+ switch (statement.type) {
258
+ case "DoStatement":
259
+ case "WhileStatement":
260
+ case "RepeatStatement":
261
+ case "FunctionDeclaration":
262
+ case "ForNumericStatement":
263
+ case "ForGenericStatement":
264
+ return [statement.body];
265
+ case "IfStatement":
266
+ return statement.clauses.map((clause) => clause.body);
267
+ default:
268
+ return [];
269
+ }
270
+ }
271
+ function replaceStatement(body, target, replacements) {
272
+ const index = body.indexOf(target);
273
+ if (index >= 0) {
274
+ body.splice(index, 1, ...replacements);
275
+ return true;
276
+ }
277
+ return body.some((statement) => statementChildren(statement).some((child) => replaceStatement(child, target, replacements)));
278
+ }
279
+ function isClosedInlineStatement(statement, resolved) {
280
+ let closed = true;
281
+ const inspect = (expression) => {
282
+ if (!isClosedInlineExpression(expression, resolved))
283
+ closed = false;
284
+ };
285
+ switch (statement.type) {
286
+ case "AssignmentStatement":
287
+ statement.variables.forEach(inspect);
288
+ statement.init.forEach(inspect);
289
+ return closed;
290
+ case "CallStatement":
291
+ inspect(statement.expression);
292
+ return closed;
293
+ default:
294
+ return false;
295
+ }
296
+ }
297
+ function hasFunctionScopeAncestor(symbolScope, functionScope) {
298
+ for (let scope = symbolScope; scope; scope = scope.parent) {
299
+ if (scope === functionScope)
300
+ return true;
301
+ }
302
+ return false;
303
+ }
304
+ function bodyUsesOnlyOwnedBindings(body, resolved, functionScope, allowReturns = false) {
305
+ let safe = true;
306
+ const visitBlock = (statements) => {
307
+ statements.forEach((statement) => {
308
+ if (statement.type === "FunctionDeclaration") {
309
+ safe = false;
310
+ return;
311
+ }
312
+ if (statement.type === "ReturnStatement" && !allowReturns) {
313
+ safe = false;
314
+ return;
315
+ }
316
+ walkStatementForOwnedBindings(statement);
317
+ });
318
+ };
319
+ const walkStatementForOwnedBindings = (statement) => {
320
+ (0, astWalk_1.walkStatement)(statement, {
321
+ onIdentifierReference: (identifier) => {
322
+ const symbol = resolved.symbolOf(identifier);
323
+ if (symbol && !hasFunctionScopeAncestor(symbol.scope, functionScope))
324
+ safe = false;
325
+ },
326
+ onFunction: () => {
327
+ safe = false;
328
+ },
329
+ onBlock: visitBlock,
330
+ });
331
+ };
332
+ visitBlock(body);
333
+ return safe;
334
+ }
335
+ function ownedLocalCount(resolved, functionScope) {
336
+ return resolved.symbols.filter((symbol) => symbol.kind !== "label" &&
337
+ hasFunctionScopeAncestor(symbol.scope, functionScope)).length;
338
+ }
339
+ /**
340
+ * Rename copied callee-owned bindings by resolved symbol identity. This keeps
341
+ * declarations and references paired even when the call site has same-spelled
342
+ * locals, and avoids treating field names as lexical bindings.
343
+ */
344
+ function alphaConvertOwnedCopies(originals, copies, resolved, functionScope) {
345
+ const owned = new Set(resolved.symbols.filter((symbol) => hasFunctionScopeAncestor(symbol.scope, functionScope)));
346
+ const unavailable = new Set([
347
+ ...resolved.symbols.map((symbol) => symbol.name),
348
+ ...resolved.globals.keys(),
349
+ ]);
350
+ const replacementNames = new Map();
351
+ let nextName = 0;
352
+ const nameOf = (symbol) => {
353
+ const existing = replacementNames.get(symbol);
354
+ if (existing !== undefined)
355
+ return existing;
356
+ let candidate;
357
+ do {
358
+ candidate = `__stormInline${String(nextName++)}`;
359
+ } while (unavailable.has(candidate));
360
+ unavailable.add(candidate);
361
+ replacementNames.set(symbol, candidate);
362
+ return candidate;
363
+ };
364
+ const visitPair = (original, copy) => {
365
+ if (!original ||
366
+ !copy ||
367
+ typeof original !== "object" ||
368
+ typeof copy !== "object")
369
+ return;
370
+ if (Array.isArray(original)) {
371
+ if (!Array.isArray(copy))
372
+ return;
373
+ original.forEach((value, index) => {
374
+ visitPair(value, copy[index]);
375
+ });
376
+ return;
377
+ }
378
+ if (original.type === "Identifier") {
379
+ if (copy.type !== "Identifier")
380
+ return;
381
+ const symbol = resolved.symbolOf(original);
382
+ if (symbol && owned.has(symbol))
383
+ copy.name = nameOf(symbol);
384
+ return;
385
+ }
386
+ Object.keys(original).forEach((key) => {
387
+ visitPair(original[key], copy[key]);
388
+ });
389
+ };
390
+ visitPair(originals, copies);
391
+ }
392
+ /** Inline a straight-line, closed function body at a statement call site. */
393
+ function inlineClosedStatementFunctions(chunk, analysis, resolved, metadata) {
394
+ let inlinedFunctions = 0;
395
+ analysis.callGraph.functions.forEach((callable) => {
396
+ const declaration = callable.declaration;
397
+ const symbol = callable.symbol;
398
+ if (!symbol ||
399
+ !declaration.isLocal ||
400
+ declaration.parameters.length !== 0 ||
401
+ symbol.references.length !== 1 ||
402
+ metadata.annotationsOf(declaration).keep)
403
+ return;
404
+ const executable = [...declaration.body];
405
+ const tail = executable.at(-1);
406
+ if (tail?.type === "ReturnStatement" && tail.arguments.length === 0)
407
+ executable.pop();
408
+ if (executable.length === 0 ||
409
+ executable.some((statement) => metadata.annotationsOf(statement).keep ||
410
+ !isClosedInlineStatement(statement, resolved)))
411
+ return;
412
+ const site = analysis.callGraph.calls.find((candidate) => candidate.owner.type === "CallStatement" &&
413
+ candidate.owner.expression === candidate.call &&
414
+ !candidate.hasUnknownTarget &&
415
+ candidate.targets.size === 1 &&
416
+ candidate.targets.has(callable) &&
417
+ candidate.call.type === "CallExpression" &&
418
+ candidate.call.arguments.length === 0 &&
419
+ candidate.call.base === symbol.references[0]);
420
+ if (!site)
421
+ return;
422
+ const replacements = executable.map((statement) => structuredClone(statement));
423
+ if (!replaceStatement(chunk.body, site.owner, replacements))
424
+ return;
425
+ metadata.replaceStatement(site.owner, replacements);
426
+ executable.forEach((source, index) => {
427
+ metadata.transferStatements([source], replacements[index]);
428
+ });
429
+ inlinedFunctions++;
430
+ });
431
+ return { changed: inlinedFunctions > 0, inlinedFunctions };
432
+ }
433
+ /**
434
+ * Inline a non-returning statement function behind a lexical block and a
435
+ * parameter-binding local. The binding statement is the semantic boundary:
436
+ * arbitrary actuals keep Lua's original left-to-right evaluation and tuple
437
+ * adjustment before the copied body starts.
438
+ */
439
+ function inlineBoundStatementFunctions(chunk, analysis, resolved, metadata, options) {
440
+ let inlinedFunctions = 0;
441
+ analysis.callGraph.functions.forEach((callable) => {
442
+ const declaration = callable.declaration;
443
+ const symbol = callable.symbol;
444
+ const functionScope = resolved.scopeOfFunction(declaration);
445
+ if (!symbol ||
446
+ !functionScope ||
447
+ !declaration.isLocal ||
448
+ declaration.parameters.length === 0 ||
449
+ declaration.parameters.some((parameter) => parameter.type !== "Identifier") ||
450
+ symbol.references.length !== 1 ||
451
+ metadata.annotationsOf(declaration).keep)
452
+ return;
453
+ const executable = [...declaration.body];
454
+ const tail = executable.at(-1);
455
+ if (tail?.type === "ReturnStatement" && tail.arguments.length === 0)
456
+ executable.pop();
457
+ if (executable.length === 0 ||
458
+ executable.some((statement) => metadata.annotationsOf(statement).keep) ||
459
+ !bodyUsesOnlyOwnedBindings(executable, resolved, functionScope))
460
+ return;
461
+ const site = analysis.callGraph.calls.find((candidate) => candidate.owner.type === "CallStatement" &&
462
+ candidate.owner.expression === candidate.call &&
463
+ !candidate.hasUnknownTarget &&
464
+ candidate.targets.size === 1 &&
465
+ candidate.targets.has(callable) &&
466
+ candidate.call.type === "CallExpression" &&
467
+ candidate.call.base === symbol.references[0]);
468
+ if (!site || site.call.type !== "CallExpression")
469
+ return;
470
+ if (declaration.parameters.length > options.maxIntroducedLocalsAt(site.owner))
471
+ return;
472
+ const binding = {
473
+ type: "LocalStatement",
474
+ variables: declaration.parameters.map((parameter) => structuredClone(parameter)),
475
+ init: site.call.arguments.map((argument) => structuredClone(argument)),
476
+ };
477
+ (0, generatedNode_1.copyNodeOrigin)(binding, site.owner);
478
+ const copiedBody = executable.map((statement) => structuredClone(statement));
479
+ alphaConvertOwnedCopies([...declaration.parameters, ...executable], [...binding.variables, ...copiedBody], resolved, functionScope);
480
+ const replacement = {
481
+ type: "DoStatement",
482
+ body: [binding, ...copiedBody],
483
+ };
484
+ (0, generatedNode_1.copyNodeOrigin)(replacement, site.owner);
485
+ if (!replaceStatement(chunk.body, site.owner, [replacement]))
486
+ return;
487
+ metadata.replaceStatement(site.owner, [replacement]);
488
+ executable.forEach((source, index) => {
489
+ metadata.transferStatements([source], copiedBody[index]);
490
+ });
491
+ inlinedFunctions++;
492
+ });
493
+ return { changed: inlinedFunctions > 0, inlinedFunctions };
494
+ }
495
+ /**
496
+ * Inline a function into `return f(...)`. Every copied callee return now exits
497
+ * the caller, which is equivalent specifically because the call occupied the
498
+ * caller's entire return tuple. A synthetic empty return preserves fallthrough.
499
+ */
500
+ function inlineTailCallFunctions(chunk, analysis, resolved, metadata, options) {
501
+ let inlinedFunctions = 0;
502
+ analysis.callGraph.functions.forEach((callable) => {
503
+ const declaration = callable.declaration;
504
+ const symbol = callable.symbol;
505
+ const functionScope = resolved.scopeOfFunction(declaration);
506
+ if (!symbol ||
507
+ !functionScope ||
508
+ !declaration.isLocal ||
509
+ declaration.parameters.some((parameter) => parameter.type !== "Identifier") ||
510
+ declaration.body.length === 0 ||
511
+ symbol.references.length !== 1 ||
512
+ metadata.annotationsOf(declaration).keep ||
513
+ !bodyUsesOnlyOwnedBindings(declaration.body, resolved, functionScope, true))
514
+ return;
515
+ const site = analysis.callGraph.calls.find((candidate) => candidate.owner.type === "ReturnStatement" &&
516
+ candidate.owner.arguments.length === 1 &&
517
+ candidate.owner.arguments[0] === candidate.call &&
518
+ !candidate.hasUnknownTarget &&
519
+ candidate.targets.size === 1 &&
520
+ candidate.targets.has(callable) &&
521
+ candidate.call.type === "CallExpression" &&
522
+ candidate.call.base === symbol.references[0]);
523
+ if (!site || site.call.type !== "CallExpression")
524
+ return;
525
+ const surplusActuals = Math.max(0, site.call.arguments.length - declaration.parameters.length);
526
+ if (ownedLocalCount(resolved, functionScope) + surplusActuals >
527
+ options.maxIntroducedLocalsAt(site.owner))
528
+ return;
529
+ const body = [];
530
+ let copiedParameters = [];
531
+ if (site.call.arguments.length > 0) {
532
+ copiedParameters = declaration.parameters.map((parameter) => structuredClone(parameter));
533
+ const unavailable = new Set([
534
+ ...resolved.symbols.map((candidate) => candidate.name),
535
+ ...resolved.globals.keys(),
536
+ ]);
537
+ while (copiedParameters.length < site.call.arguments.length) {
538
+ let index = copiedParameters.length;
539
+ let name = `__stormDiscard${String(index)}`;
540
+ while (unavailable.has(name)) {
541
+ index++;
542
+ name = `__stormDiscard${String(index)}`;
543
+ }
544
+ unavailable.add(name);
545
+ copiedParameters.push({ type: "Identifier", name });
546
+ }
547
+ const binding = {
548
+ type: "LocalStatement",
549
+ variables: copiedParameters,
550
+ init: site.call.arguments.map((argument) => structuredClone(argument)),
551
+ };
552
+ (0, generatedNode_1.copyNodeOrigin)(binding, site.owner);
553
+ body.push(binding);
554
+ }
555
+ const copiedBody = declaration.body.map((statement) => structuredClone(statement));
556
+ alphaConvertOwnedCopies([...declaration.parameters, ...declaration.body], [
557
+ ...copiedParameters.slice(0, declaration.parameters.length),
558
+ ...copiedBody,
559
+ ], resolved, functionScope);
560
+ body.push(...copiedBody);
561
+ if (declaration.body.at(-1)?.type !== "ReturnStatement") {
562
+ const fallthrough = {
563
+ type: "ReturnStatement",
564
+ arguments: [],
565
+ };
566
+ (0, generatedNode_1.copyNodeOrigin)(fallthrough, site.owner);
567
+ body.push(fallthrough);
568
+ }
569
+ const replacement = { type: "DoStatement", body };
570
+ (0, generatedNode_1.copyNodeOrigin)(replacement, site.owner);
571
+ if (!replaceStatement(chunk.body, site.owner, [replacement]))
572
+ return;
573
+ metadata.replaceStatement(site.owner, [replacement]);
574
+ declaration.body.forEach((source, index) => {
575
+ metadata.transferStatements([source], copiedBody[index]);
576
+ });
577
+ inlinedFunctions++;
578
+ });
579
+ return { changed: inlinedFunctions > 0, inlinedFunctions };
580
+ }
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildRequireWrapperAst = buildRequireWrapperAst;
4
+ const identifier = (name) => ({
5
+ type: "Identifier",
6
+ name,
7
+ });
8
+ const member = (base, name) => ({
9
+ type: "MemberExpression",
10
+ indexer: ".",
11
+ identifier: identifier(name),
12
+ base,
13
+ });
14
+ const index = (base, key) => ({ type: "IndexExpression", base, index: key });
15
+ const loaded = () => member(identifier("package"), "loaded");
16
+ const loadedAtM = () => index(loaded(), identifier("m"));
17
+ const assignment = (variable, value) => ({
18
+ type: "AssignmentStatement",
19
+ variables: [variable],
20
+ init: [value],
21
+ });
22
+ const logical = (operator, left, right) => ({
23
+ type: "LogicalExpression",
24
+ operator,
25
+ left,
26
+ right,
27
+ });
28
+ const truth = {
29
+ type: "BooleanLiteral",
30
+ value: true,
31
+ raw: "true",
32
+ };
33
+ function moduleClause(moduleName) {
34
+ const moduleString = {
35
+ type: "StringLiteral",
36
+ value: moduleName,
37
+ raw: JSON.stringify(moduleName),
38
+ };
39
+ const moduleBody = { type: "ModuleSplice", moduleName };
40
+ const loader = {
41
+ type: "FunctionDeclaration",
42
+ identifier: null,
43
+ isLocal: false,
44
+ parameters: [],
45
+ // luaparseの公開型は拡張ノードを含まない。コード生成前だけ存在する内部ASTとして
46
+ // FunctionDeclarationのbodyへ保持し、printer側でModuleSpliceを明示的に処理する。
47
+ body: [moduleBody],
48
+ };
49
+ const callLoader = {
50
+ type: "CallExpression",
51
+ base: loader,
52
+ arguments: [],
53
+ };
54
+ return {
55
+ type: "IfStatement",
56
+ clauses: [
57
+ {
58
+ type: "IfClause",
59
+ condition: {
60
+ type: "BinaryExpression",
61
+ operator: "==",
62
+ left: identifier("m"),
63
+ right: moduleString,
64
+ },
65
+ body: [assignment(identifier("r"), callLoader)],
66
+ },
67
+ ],
68
+ };
69
+ }
70
+ /** require互換関数を、入力ソースに由来しない合成ASTとして構築する。 */
71
+ function buildRequireWrapperAst(moduleNames) {
72
+ const emptyLoadedTable = {
73
+ type: "TableConstructorExpression",
74
+ fields: [],
75
+ };
76
+ const packageFallback = {
77
+ type: "TableConstructorExpression",
78
+ fields: [
79
+ {
80
+ type: "TableKeyString",
81
+ key: identifier("loaded"),
82
+ value: emptyLoadedTable,
83
+ },
84
+ ],
85
+ };
86
+ return {
87
+ type: "FunctionDeclaration",
88
+ identifier: identifier("require"),
89
+ isLocal: false,
90
+ parameters: [identifier("m"), identifier("r")],
91
+ body: [
92
+ assignment(identifier("package"), logical("or", identifier("package"), packageFallback)),
93
+ {
94
+ type: "IfStatement",
95
+ clauses: [
96
+ {
97
+ type: "IfClause",
98
+ condition: loadedAtM(),
99
+ body: [{ type: "ReturnStatement", arguments: [loadedAtM()] }],
100
+ },
101
+ ],
102
+ },
103
+ ...moduleNames.map(moduleClause),
104
+ assignment(loadedAtM(), logical("or", logical("or", loadedAtM(), identifier("r")), truth)),
105
+ { type: "ReturnStatement", arguments: [loadedAtM()] },
106
+ ],
107
+ };
108
+ }