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,314 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SourceMetadata = void 0;
4
+ exports.isPreservedComment = isPreservedComment;
5
+ const NONE = {
6
+ keep: false,
7
+ keepName: false,
8
+ exported: false,
9
+ };
10
+ function rangeOf(node) {
11
+ return node.range;
12
+ }
13
+ function statementChildren(statement) {
14
+ switch (statement.type) {
15
+ case "DoStatement":
16
+ case "WhileStatement":
17
+ case "RepeatStatement":
18
+ case "FunctionDeclaration":
19
+ case "ForNumericStatement":
20
+ case "ForGenericStatement":
21
+ return [statement.body];
22
+ case "IfStatement":
23
+ return statement.clauses.map((clause) => clause.body);
24
+ default:
25
+ return [];
26
+ }
27
+ }
28
+ function collectStatements(body) {
29
+ const statements = [];
30
+ const visit = (block) => {
31
+ block.forEach((statement) => {
32
+ statements.push(statement);
33
+ statementChildren(statement).forEach(visit);
34
+ });
35
+ };
36
+ visit(body);
37
+ return statements.sort((a, b) => (rangeOf(a)?.[0] ?? 0) - (rangeOf(b)?.[0] ?? 0));
38
+ }
39
+ function hasBlankLine(text) {
40
+ return /\r?\n[\t ]*\r?\n/.test(text);
41
+ }
42
+ function parseAnnotations(comments) {
43
+ let keep = false;
44
+ let keepName = false;
45
+ let exported = false;
46
+ comments.forEach((comment) => {
47
+ const matches = comment.raw.matchAll(/--@storm\s+([^\r\n]*)/g);
48
+ for (const match of matches) {
49
+ const directive = match[1].trim();
50
+ if (directive === "keep") {
51
+ keep = true;
52
+ }
53
+ else if (directive === "keep-name") {
54
+ keepName = true;
55
+ }
56
+ else if (directive === "export") {
57
+ exported = true;
58
+ keep = true;
59
+ keepName = true;
60
+ }
61
+ else {
62
+ throw new Error(`Unknown storm annotation: ${directive || "(empty)"}`);
63
+ }
64
+ }
65
+ });
66
+ return { keep, keepName, exported };
67
+ }
68
+ function parseEmmyLua(comments) {
69
+ const directives = [];
70
+ comments.forEach((comment) => {
71
+ for (const match of comment.raw.matchAll(/---@([\w-]+)\s*([^\r\n]*)/g)) {
72
+ const directive = match[1];
73
+ const value = match[2].trim();
74
+ if (directive === "class") {
75
+ const parsed = /^([^\s:]+)(?:\s*:\s*([^\s]+))?/.exec(value);
76
+ if (parsed)
77
+ directives.push({
78
+ kind: "class",
79
+ name: parsed[1],
80
+ ...(parsed[2] ? { base: parsed[2] } : {}),
81
+ comment,
82
+ });
83
+ }
84
+ else if (directive === "field") {
85
+ const parsed = /^(?:(?:public|protected|private|package)\s+)?(?:\([^)]*\)\s*)?([^\s]+)\s+(.+)$/.exec(value);
86
+ if (parsed)
87
+ directives.push({
88
+ kind: "field",
89
+ name: parsed[1],
90
+ valueType: parsed[2].trim(),
91
+ comment,
92
+ });
93
+ }
94
+ else if (directive === "param") {
95
+ const parsed = /^(\S+)\s+(.+)$/.exec(value);
96
+ if (parsed)
97
+ directives.push({
98
+ kind: "param",
99
+ name: parsed[1],
100
+ valueType: parsed[2].trim(),
101
+ comment,
102
+ });
103
+ }
104
+ else if (directive === "return" || directive === "type") {
105
+ if (value)
106
+ directives.push({ kind: directive, valueType: value, comment });
107
+ }
108
+ else if (directive === "alias") {
109
+ const parsed = /^(\S+)\s+(.+)$/.exec(value);
110
+ if (parsed)
111
+ directives.push({
112
+ kind: "alias",
113
+ name: parsed[1],
114
+ valueType: parsed[2].trim(),
115
+ comment,
116
+ });
117
+ }
118
+ else if (directive === "enum") {
119
+ const name = value.split(/\s+/)[0];
120
+ if (name)
121
+ directives.push({ kind: "enum", name, comment });
122
+ }
123
+ else {
124
+ directives.push({ kind: "other", directive, value, comment });
125
+ }
126
+ }
127
+ });
128
+ return directives;
129
+ }
130
+ function isPreservedComment(comment) {
131
+ return comment.raw.includes("--#") || comment.raw.includes("[[#");
132
+ }
133
+ /** Source comments and annotations associated with statement nodes by range. */
134
+ class SourceMetadata {
135
+ before = new WeakMap();
136
+ detachedBefore = new WeakMap();
137
+ trailing = new WeakMap();
138
+ annotations = new WeakMap();
139
+ emmyLua = new WeakMap();
140
+ afterModule = [];
141
+ statementOfIdentifier = new WeakMap();
142
+ constructor(chunk, sourceText) {
143
+ const statements = collectStatements(chunk.body);
144
+ statements.forEach((statement) => {
145
+ if (statement.type === "LocalStatement") {
146
+ statement.variables.forEach((identifier) => this.statementOfIdentifier.set(identifier, statement));
147
+ }
148
+ else if (statement.type === "FunctionDeclaration" &&
149
+ statement.identifier?.type === "Identifier") {
150
+ this.statementOfIdentifier.set(statement.identifier, statement);
151
+ }
152
+ else if (statement.type === "AssignmentStatement") {
153
+ statement.variables.forEach((variable) => {
154
+ if (variable.type === "Identifier") {
155
+ this.statementOfIdentifier.set(variable, statement);
156
+ }
157
+ });
158
+ }
159
+ });
160
+ const comments = [...(chunk.comments ?? [])].sort((a, b) => (rangeOf(a)?.[0] ?? 0) - (rangeOf(b)?.[0] ?? 0));
161
+ let index = 0;
162
+ while (index < comments.length) {
163
+ const group = [comments[index++]];
164
+ while (index < comments.length) {
165
+ const previousEnd = rangeOf(group[group.length - 1])?.[1];
166
+ const nextStart = rangeOf(comments[index])?.[0];
167
+ if (previousEnd === undefined || nextStart === undefined)
168
+ break;
169
+ const gap = sourceText.slice(previousEnd, nextStart);
170
+ if (/^\s*$/.test(gap) && !hasBlankLine(gap)) {
171
+ group.push(comments[index++]);
172
+ }
173
+ else {
174
+ break;
175
+ }
176
+ }
177
+ const first = group[0];
178
+ const last = group[group.length - 1];
179
+ const groupAnnotations = parseAnnotations(group);
180
+ const groupEmmyLua = parseEmmyLua(group);
181
+ const preceding = [...statements]
182
+ .reverse()
183
+ .find((statement) => (rangeOf(statement)?.[1] ?? Infinity) <=
184
+ (rangeOf(first)?.[0] ?? -1));
185
+ if (preceding?.loc?.end.line !== undefined &&
186
+ preceding.loc.end.line === first.loc?.start.line) {
187
+ this.trailing.set(preceding, [
188
+ ...(this.trailing.get(preceding) ?? []),
189
+ ...group,
190
+ ]);
191
+ continue;
192
+ }
193
+ const following = statements.find((statement) => (rangeOf(statement)?.[0] ?? -1) >= (rangeOf(last)?.[1] ?? Infinity));
194
+ if (following) {
195
+ const gap = sourceText.slice(rangeOf(last)?.[1] ?? 0, rangeOf(following)?.[0] ?? 0);
196
+ if (/^\s*$/.test(gap) && !hasBlankLine(gap)) {
197
+ this.before.set(following, [
198
+ ...(this.before.get(following) ?? []),
199
+ ...group,
200
+ ]);
201
+ if (groupAnnotations.keep ||
202
+ groupAnnotations.keepName ||
203
+ groupAnnotations.exported) {
204
+ this.annotations.set(following, groupAnnotations);
205
+ }
206
+ if (groupEmmyLua.length > 0)
207
+ this.emmyLua.set(following, groupEmmyLua);
208
+ }
209
+ else {
210
+ this.detachedBefore.set(following, [
211
+ ...(this.detachedBefore.get(following) ?? []),
212
+ ...group,
213
+ ]);
214
+ }
215
+ }
216
+ else {
217
+ this.afterModule.push(...group);
218
+ }
219
+ }
220
+ }
221
+ annotationsOf(statement) {
222
+ return this.annotations.get(statement) ?? NONE;
223
+ }
224
+ annotationsOfIdentifier(identifier) {
225
+ const statement = this.statementOfIdentifier.get(identifier);
226
+ return statement ? this.annotationsOf(statement) : NONE;
227
+ }
228
+ emmyLuaOf(statement) {
229
+ return this.emmyLua.get(statement) ?? [];
230
+ }
231
+ emmyLuaOfIdentifier(identifier) {
232
+ const statement = this.statementOfIdentifier.get(identifier);
233
+ return statement ? this.emmyLuaOf(statement) : [];
234
+ }
235
+ beforeOf(statement) {
236
+ return [
237
+ ...(this.detachedBefore.get(statement) ?? []),
238
+ ...(this.before.get(statement) ?? []),
239
+ ];
240
+ }
241
+ trailingOf(statement) {
242
+ return this.trailing.get(statement) ?? [];
243
+ }
244
+ afterModuleComments() {
245
+ return this.afterModule;
246
+ }
247
+ transferStatements(sources, target) {
248
+ const before = sources.flatMap((statement) => [
249
+ ...(this.detachedBefore.get(statement) ?? []),
250
+ ...(this.before.get(statement) ?? []),
251
+ ]);
252
+ const trailing = sources.flatMap((statement) => this.trailing.get(statement) ?? []);
253
+ if (before.length)
254
+ this.before.set(target, before);
255
+ if (trailing.length)
256
+ this.trailing.set(target, trailing);
257
+ const annotations = sources.map((statement) => this.annotationsOf(statement));
258
+ const combined = {
259
+ keep: annotations.some((value) => value.keep),
260
+ keepName: annotations.some((value) => value.keepName),
261
+ exported: annotations.some((value) => value.exported),
262
+ };
263
+ if (combined.keep || combined.keepName || combined.exported) {
264
+ this.annotations.set(target, combined);
265
+ }
266
+ const emmyLua = sources.flatMap((statement) => this.emmyLuaOf(statement));
267
+ if (emmyLua.length > 0)
268
+ this.emmyLua.set(target, emmyLua);
269
+ }
270
+ /**
271
+ * 1つの文を複数文へ置換するとき、文境界に属する情報を外側の境界へ移す。
272
+ * leading/detachedは最初、trailingは最後へ置くことで、置換後もコメントの
273
+ * 前後関係を変えない。アノテーションは変換判断に使った後も、後続パスが
274
+ * 同じ保護指定を観測できるよう全置換文へ引き継ぐ。
275
+ */
276
+ replaceStatement(source, replacements) {
277
+ if (replacements.length === 0)
278
+ return;
279
+ const first = replacements[0];
280
+ const last = replacements[replacements.length - 1];
281
+ const detached = this.detachedBefore.get(source);
282
+ const before = this.before.get(source);
283
+ const trailing = this.trailing.get(source);
284
+ if (detached?.length)
285
+ this.detachedBefore.set(first, detached);
286
+ if (before?.length)
287
+ this.before.set(first, before);
288
+ if (trailing?.length)
289
+ this.trailing.set(last, trailing);
290
+ const annotations = this.annotations.get(source);
291
+ if (annotations) {
292
+ replacements.forEach((statement) => this.annotations.set(statement, annotations));
293
+ }
294
+ const emmyLua = this.emmyLua.get(source);
295
+ if (emmyLua) {
296
+ replacements.forEach((statement) => this.emmyLua.set(statement, emmyLua));
297
+ }
298
+ }
299
+ removeStatement(statement, nextStatement) {
300
+ const detached = this.detachedBefore.get(statement) ?? [];
301
+ if (detached.length === 0)
302
+ return;
303
+ if (nextStatement) {
304
+ this.detachedBefore.set(nextStatement, [
305
+ ...detached,
306
+ ...(this.detachedBefore.get(nextStatement) ?? []),
307
+ ]);
308
+ }
309
+ else {
310
+ this.afterModule.push(...detached);
311
+ }
312
+ }
313
+ }
314
+ exports.SourceMetadata = SourceMetadata;
@@ -0,0 +1,259 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.analyzeStatementDataflow = analyzeStatementDataflow;
4
+ const symbolLiveness_1 = require("./symbolLiveness");
5
+ /** Shared CFG liveness and lexical statement-dependence analysis used by every scheduler rewrite. */
6
+ function analyzeStatementDataflow(chunk, facts, valueFlow) {
7
+ if (facts.generation !== valueFlow.version) {
8
+ throw new Error("Statement dataflow requires one AST generation");
9
+ }
10
+ const controlFlow = valueFlow.controlFlow;
11
+ const symbolIds = new Map();
12
+ const globalIds = new Map();
13
+ const dependencies = [];
14
+ const dependenciesByFrom = new WeakMap();
15
+ const bodyOf = new WeakMap();
16
+ const indexOf = new WeakMap();
17
+ const symbolId = (symbol) => {
18
+ const existing = symbolIds.get(symbol);
19
+ if (existing !== undefined)
20
+ return existing;
21
+ const id = symbolIds.size;
22
+ symbolIds.set(symbol, id);
23
+ return id;
24
+ };
25
+ const globalId = (binding) => {
26
+ const existing = globalIds.get(binding);
27
+ if (existing !== undefined)
28
+ return existing;
29
+ const id = globalIds.size;
30
+ globalIds.set(binding, id);
31
+ return id;
32
+ };
33
+ const locationAccess = (location, owner) => {
34
+ switch (location.kind) {
35
+ case "local":
36
+ case "parameter":
37
+ case "upvalue":
38
+ return {
39
+ family: "symbol",
40
+ identity: `symbol:${String(symbolId(location.symbol))}`,
41
+ };
42
+ case "global":
43
+ return {
44
+ family: "global",
45
+ identity: `global:${String(globalId(location.binding))}`,
46
+ };
47
+ case "external":
48
+ return { family: "external", identity: "external:*" };
49
+ case "table": {
50
+ const point = controlFlow.pointOf(owner);
51
+ const allocation = point
52
+ ? valueFlow.allocationOfBase(location.base, point)
53
+ : undefined;
54
+ if (!allocation)
55
+ return { family: "external", identity: "external:*" };
56
+ const tableIdentity = `allocation:${String(allocation.id)}`;
57
+ const tableKey = location.key.kind === "static" ? location.key.value : "*";
58
+ return {
59
+ family: "table",
60
+ identity: `${tableIdentity}:${tableKey}`,
61
+ tableIdentity,
62
+ tableKey,
63
+ };
64
+ }
65
+ }
66
+ };
67
+ const accessesOf = (operations, kind) => {
68
+ const result = [];
69
+ operations.forEach((operation) => {
70
+ if (kind === "read" &&
71
+ (operation.kind === "read" || operation.kind === "table-read"))
72
+ result.push(locationAccess(operation.location, operation.owner));
73
+ if (kind === "write" &&
74
+ (operation.kind === "write" ||
75
+ operation.kind === "declare" ||
76
+ operation.kind === "table-write"))
77
+ result.push(locationAccess(operation.location, operation.owner));
78
+ });
79
+ return deduplicateAccesses(result);
80
+ };
81
+ const summarize = (statement) => {
82
+ const operations = facts.operationsWithin(statement);
83
+ const expressionEffects = operations.flatMap((operation) => {
84
+ const origin = operation.origin;
85
+ return isExpression(origin) ? [facts.expressionFact(origin)] : [];
86
+ });
87
+ return {
88
+ reads: accessesOf(operations, "read"),
89
+ writes: accessesOf(operations, "write"),
90
+ hasCall: operations.some((operation) => operation.kind === "call"),
91
+ mayError: expressionEffects.some((fact) => fact?.effects.mayError.value === "may"),
92
+ mayMetamethod: expressionEffects.some((fact) => fact?.effects.mayInvokeMetamethod.value === "may"),
93
+ allocates: operations.some((operation) => operation.kind === "allocate"),
94
+ controlsFlow: isControlStatement(statement),
95
+ declares: operations.some((operation) => operation.kind === "declare"),
96
+ };
97
+ };
98
+ const liveness = (0, symbolLiveness_1.analyzeSymbolLiveness)(controlFlow, facts);
99
+ const analyzeBody = (body) => {
100
+ body.forEach((statement, index) => {
101
+ bodyOf.set(statement, body);
102
+ indexOf.set(statement, index);
103
+ });
104
+ const summaries = body.map(summarize);
105
+ for (let left = 0; left < body.length; left++) {
106
+ for (let right = left + 1; right < body.length; right++) {
107
+ dependencies.push(...edgesBetween(body[left], summaries[left], body[right], summaries[right]));
108
+ }
109
+ }
110
+ body.forEach((statement) => {
111
+ childBodies(statement).forEach(analyzeBody);
112
+ });
113
+ };
114
+ analyzeBody(chunk.body);
115
+ dependencies.forEach((edge) => {
116
+ let byTarget = dependenciesByFrom.get(edge.from);
117
+ if (!byTarget) {
118
+ byTarget = new WeakMap();
119
+ dependenciesByFrom.set(edge.from, byTarget);
120
+ }
121
+ const current = byTarget.get(edge.to) ?? [];
122
+ byTarget.set(edge.to, [...current, edge]);
123
+ });
124
+ return {
125
+ generation: facts.generation,
126
+ controlFlow,
127
+ symbolLiveness: liveness,
128
+ dependencies,
129
+ liveIn: (node) => liveness.liveIn(node),
130
+ liveOut: (node) => liveness.liveOut(node),
131
+ isLiveBefore: (statement, symbol) => {
132
+ const node = controlFlow.nodeOf(statement);
133
+ return !!node && liveness.liveIn(node).has(symbol);
134
+ },
135
+ isLiveAfter: (statement, symbol) => {
136
+ const node = controlFlow.nodeOf(statement);
137
+ return !!node && liveness.liveOut(node).has(symbol);
138
+ },
139
+ dependenciesBetween: (first, last) => dependenciesByFrom.get(first)?.get(last) ?? [],
140
+ canMoveBefore: (moving, target) => {
141
+ const body = bodyOf.get(moving);
142
+ if (!body || body !== bodyOf.get(target)) {
143
+ return { allowed: false, reason: "different-block" };
144
+ }
145
+ const movingIndex = indexOf.get(moving);
146
+ const targetIndex = indexOf.get(target);
147
+ if (movingIndex === undefined ||
148
+ targetIndex === undefined ||
149
+ targetIndex >= movingIndex)
150
+ return { allowed: false, reason: "different-block" };
151
+ const point = controlFlow.pointOf(moving);
152
+ if (!point ||
153
+ controlFlow.unknownEdges.some((edge) => edge.from.unit === point.unit))
154
+ return { allowed: false, reason: "unknown-edge" };
155
+ for (let index = targetIndex; index < movingIndex; index++) {
156
+ const obstacle = body[index];
157
+ const edge = dependencies.find((candidate) => candidate.from === obstacle && candidate.to === moving);
158
+ if (edge)
159
+ return { allowed: false, reason: edge.kind };
160
+ }
161
+ return { allowed: true };
162
+ },
163
+ };
164
+ }
165
+ function edgesBetween(first, left, last, right) {
166
+ const edges = [];
167
+ const addHazard = (kind, a, b) => {
168
+ const locations = conflictingIdentities(a, b);
169
+ if (locations.length > 0)
170
+ edges.push({ from: first, to: last, kind, locations });
171
+ };
172
+ addHazard("read-after-write", left.writes, right.reads);
173
+ addHazard("write-after-read", left.reads, right.writes);
174
+ addHazard("write-after-write", left.writes, right.writes);
175
+ if (left.hasCall && right.hasCall)
176
+ edges.push({ from: first, to: last, kind: "call-order", locations: [] });
177
+ if (left.mayError && right.mayError)
178
+ edges.push({ from: first, to: last, kind: "error-order", locations: [] });
179
+ if (left.mayMetamethod && right.mayMetamethod)
180
+ edges.push({
181
+ from: first,
182
+ to: last,
183
+ kind: "metamethod-order",
184
+ locations: [],
185
+ });
186
+ if (left.allocates && right.allocates)
187
+ edges.push({
188
+ from: first,
189
+ to: last,
190
+ kind: "allocation-order",
191
+ locations: [],
192
+ });
193
+ if (left.controlsFlow || right.controlsFlow)
194
+ edges.push({ from: first, to: last, kind: "control-order", locations: [] });
195
+ if (left.declares || right.declares)
196
+ edges.push({ from: first, to: last, kind: "scope-order", locations: [] });
197
+ return edges;
198
+ }
199
+ function conflictingIdentities(left, right) {
200
+ const conflicts = new Set();
201
+ left.forEach((a) => {
202
+ right.forEach((b) => {
203
+ if (accessesConflict(a, b))
204
+ conflicts.add(a.identity === "external:*" ? b.identity : a.identity);
205
+ });
206
+ });
207
+ return [...conflicts].sort();
208
+ }
209
+ function accessesConflict(left, right) {
210
+ if (left.family === "external" || right.family === "external")
211
+ return true;
212
+ if (left.family !== right.family)
213
+ return false;
214
+ if (left.family !== "table")
215
+ return left.identity === right.identity;
216
+ return (left.tableIdentity === right.tableIdentity &&
217
+ (left.tableKey === "*" ||
218
+ right.tableKey === "*" ||
219
+ left.tableKey === right.tableKey));
220
+ }
221
+ function deduplicateAccesses(accesses) {
222
+ const seen = new Set();
223
+ return accesses.filter((access) => {
224
+ if (seen.has(access.identity))
225
+ return false;
226
+ seen.add(access.identity);
227
+ return true;
228
+ });
229
+ }
230
+ function isExpression(node) {
231
+ return (node.type.endsWith("Expression") ||
232
+ node.type.endsWith("Literal") ||
233
+ node.type === "Identifier");
234
+ }
235
+ function isControlStatement(statement) {
236
+ return (statement.type === "IfStatement" ||
237
+ statement.type === "WhileStatement" ||
238
+ statement.type === "RepeatStatement" ||
239
+ statement.type === "ForNumericStatement" ||
240
+ statement.type === "ForGenericStatement" ||
241
+ statement.type === "BreakStatement" ||
242
+ statement.type === "ReturnStatement" ||
243
+ statement.type === "GotoStatement" ||
244
+ statement.type === "LabelStatement");
245
+ }
246
+ function childBodies(statement) {
247
+ switch (statement.type) {
248
+ case "DoStatement":
249
+ case "WhileStatement":
250
+ case "RepeatStatement":
251
+ case "ForNumericStatement":
252
+ case "ForGenericStatement":
253
+ return [statement.body];
254
+ case "IfStatement":
255
+ return statement.clauses.map((clause) => clause.body);
256
+ default:
257
+ return [];
258
+ }
259
+ }