@shd101wyy/yo 0.1.29 → 0.1.30

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 (174) hide show
  1. package/.github/skills/yo-async-effects/SKILL.md +3 -3
  2. package/.github/skills/yo-async-effects/async-effects-recipes.md +19 -11
  3. package/.github/skills/yo-core-patterns/core-patterns-cheatsheet.md +33 -13
  4. package/.github/skills/yo-project-workflow/workflow-cheatsheet.md +1 -1
  5. package/.github/skills/yo-syntax/syntax-cheatsheet.md +59 -21
  6. package/README.md +4 -3
  7. package/out/cjs/index.cjs +771 -676
  8. package/out/cjs/yo-cli.cjs +1003 -898
  9. package/out/cjs/yo-lsp.cjs +834 -739
  10. package/out/esm/index.mjs +716 -621
  11. package/out/types/src/codegen/exprs/async.d.ts +2 -0
  12. package/out/types/src/codegen/exprs/await.d.ts +1 -0
  13. package/out/types/src/codegen/exprs/closures.d.ts +4 -0
  14. package/out/types/src/codegen/functions/context.d.ts +6 -0
  15. package/out/types/src/env.d.ts +2 -0
  16. package/out/types/src/evaluator/builtins/pragma.d.ts +9 -0
  17. package/out/types/src/evaluator/builtins/unsafe.d.ts +8 -0
  18. package/out/types/src/evaluator/context.d.ts +2 -0
  19. package/out/types/src/evaluator/index.d.ts +1 -1
  20. package/out/types/src/evaluator/memory-safety.d.ts +14 -0
  21. package/out/types/src/evaluator/types/flowability.d.ts +6 -0
  22. package/out/types/src/expr-traversal.d.ts +1 -0
  23. package/out/types/src/expr.d.ts +4 -1
  24. package/out/types/src/public-safe-report.d.ts +19 -0
  25. package/out/types/src/tests/comptime-ref-gate.test.d.ts +1 -0
  26. package/out/types/src/tests/pragma-validation.test.d.ts +1 -0
  27. package/out/types/src/tests/public-safe-report.test.d.ts +1 -0
  28. package/out/types/src/tests/type-representation-pointer.test.d.ts +1 -0
  29. package/out/types/src/tests/unsafe-gate.test.d.ts +1 -0
  30. package/out/types/src/tests/unsafe-report-classify.test.d.ts +1 -0
  31. package/out/types/src/types/definitions.d.ts +2 -0
  32. package/out/types/src/types/utils.d.ts +4 -0
  33. package/out/types/src/unsafe-report.d.ts +29 -0
  34. package/out/types/src/value.d.ts +1 -0
  35. package/out/types/tsconfig.tsbuildinfo +1 -1
  36. package/package.json +1 -1
  37. package/scripts/add-pragma-for-pointer-decls.ts +134 -0
  38. package/scripts/add-pragma.ts +58 -0
  39. package/scripts/migrate-amp-method-calls.ts +186 -0
  40. package/scripts/migrate-clone-calls.ts +93 -0
  41. package/scripts/migrate-get-unwrap.ts +166 -0
  42. package/scripts/migrate-index-patterns.ts +210 -0
  43. package/scripts/migrate-index-trait.ts +142 -0
  44. package/scripts/migrate-iterator.ts +150 -0
  45. package/scripts/migrate-self-ptr.ts +220 -0
  46. package/scripts/migrate-skip-pragmas.ts +109 -0
  47. package/scripts/migrate-tostring.ts +134 -0
  48. package/scripts/trim-pragma.ts +130 -0
  49. package/scripts/wrap-extern-calls.ts +161 -0
  50. package/std/alg/hash.yo +3 -2
  51. package/std/allocator.yo +6 -5
  52. package/std/async.yo +2 -2
  53. package/std/collections/array_list.yo +59 -40
  54. package/std/collections/btree_map.yo +19 -18
  55. package/std/collections/deque.yo +9 -8
  56. package/std/collections/hash_map.yo +101 -13
  57. package/std/collections/hash_set.yo +5 -4
  58. package/std/collections/linked_list.yo +39 -4
  59. package/std/collections/ordered_map.yo +3 -3
  60. package/std/collections/priority_queue.yo +14 -13
  61. package/std/crypto/md5.yo +2 -1
  62. package/std/crypto/random.yo +16 -15
  63. package/std/crypto/sha256.yo +2 -1
  64. package/std/encoding/base64.yo +14 -14
  65. package/std/encoding/hex.yo +3 -3
  66. package/std/encoding/json.yo +59 -10
  67. package/std/encoding/punycode.yo +24 -23
  68. package/std/encoding/toml.yo +4 -3
  69. package/std/encoding/utf16.yo +2 -2
  70. package/std/env.yo +43 -28
  71. package/std/error.yo +6 -6
  72. package/std/fmt/display.yo +2 -2
  73. package/std/fmt/index.yo +6 -5
  74. package/std/fmt/to_string.yo +39 -38
  75. package/std/fmt/writer.yo +9 -8
  76. package/std/fs/dir.yo +34 -33
  77. package/std/fs/file.yo +52 -51
  78. package/std/fs/metadata.yo +10 -9
  79. package/std/fs/temp.yo +24 -13
  80. package/std/fs/walker.yo +10 -9
  81. package/std/gc.yo +1 -0
  82. package/std/glob.yo +7 -7
  83. package/std/http/client.yo +15 -14
  84. package/std/http/http.yo +6 -6
  85. package/std/http/index.yo +1 -1
  86. package/std/imm/list.yo +33 -0
  87. package/std/imm/map.yo +2 -1
  88. package/std/imm/set.yo +1 -0
  89. package/std/imm/sorted_map.yo +1 -0
  90. package/std/imm/sorted_set.yo +1 -0
  91. package/std/imm/string.yo +27 -23
  92. package/std/imm/vec.yo +18 -2
  93. package/std/io/reader.yo +2 -1
  94. package/std/io/writer.yo +3 -2
  95. package/std/libc/assert.yo +1 -0
  96. package/std/libc/ctype.yo +1 -0
  97. package/std/libc/dirent.yo +1 -0
  98. package/std/libc/errno.yo +1 -0
  99. package/std/libc/fcntl.yo +1 -0
  100. package/std/libc/float.yo +1 -0
  101. package/std/libc/limits.yo +1 -0
  102. package/std/libc/math.yo +1 -0
  103. package/std/libc/signal.yo +1 -0
  104. package/std/libc/stdatomic.yo +1 -0
  105. package/std/libc/stdint.yo +1 -0
  106. package/std/libc/stdio.yo +1 -0
  107. package/std/libc/stdlib.yo +1 -0
  108. package/std/libc/string.yo +1 -0
  109. package/std/libc/sys/stat.yo +1 -0
  110. package/std/libc/time.yo +1 -0
  111. package/std/libc/unistd.yo +1 -0
  112. package/std/libc/wctype.yo +1 -0
  113. package/std/libc/windows.yo +2 -0
  114. package/std/log.yo +7 -6
  115. package/std/net/addr.yo +5 -4
  116. package/std/net/dns.yo +7 -6
  117. package/std/net/errors.yo +8 -8
  118. package/std/net/tcp.yo +19 -18
  119. package/std/net/udp.yo +13 -12
  120. package/std/os/signal.yo +3 -3
  121. package/std/path.yo +1 -0
  122. package/std/prelude.yo +353 -182
  123. package/std/process/command.yo +40 -23
  124. package/std/process/index.yo +2 -1
  125. package/std/regex/compiler.yo +10 -9
  126. package/std/regex/index.yo +41 -41
  127. package/std/regex/match.yo +2 -2
  128. package/std/regex/parser.yo +21 -21
  129. package/std/regex/vm.yo +42 -41
  130. package/std/string/string.yo +95 -40
  131. package/std/string/string_builder.yo +9 -9
  132. package/std/string/unicode.yo +50 -49
  133. package/std/sync/channel.yo +2 -1
  134. package/std/sync/cond.yo +5 -4
  135. package/std/sync/mutex.yo +4 -3
  136. package/std/sys/advise.yo +1 -0
  137. package/std/sys/bufio/buf_reader.yo +17 -16
  138. package/std/sys/bufio/buf_writer.yo +10 -9
  139. package/std/sys/clock.yo +1 -0
  140. package/std/sys/copy.yo +1 -0
  141. package/std/sys/dir.yo +10 -9
  142. package/std/sys/dns.yo +6 -5
  143. package/std/sys/errors.yo +11 -11
  144. package/std/sys/events.yo +1 -0
  145. package/std/sys/externs.yo +38 -37
  146. package/std/sys/file.yo +17 -16
  147. package/std/sys/future.yo +4 -3
  148. package/std/sys/iov.yo +1 -0
  149. package/std/sys/mmap.yo +1 -0
  150. package/std/sys/path.yo +1 -0
  151. package/std/sys/perm.yo +2 -1
  152. package/std/sys/pipe.yo +1 -0
  153. package/std/sys/process.yo +5 -4
  154. package/std/sys/signal.yo +1 -0
  155. package/std/sys/socketpair.yo +1 -0
  156. package/std/sys/sockinfo.yo +1 -0
  157. package/std/sys/statfs.yo +2 -1
  158. package/std/sys/statx.yo +1 -0
  159. package/std/sys/sysinfo.yo +1 -0
  160. package/std/sys/tcp.yo +15 -14
  161. package/std/sys/temp.yo +1 -0
  162. package/std/sys/time.yo +2 -1
  163. package/std/sys/timer.yo +6 -6
  164. package/std/sys/tty.yo +2 -1
  165. package/std/sys/udp.yo +13 -12
  166. package/std/sys/unix.yo +12 -11
  167. package/std/testing/bench.yo +4 -3
  168. package/std/thread.yo +7 -6
  169. package/std/time/datetime.yo +18 -15
  170. package/std/time/duration.yo +11 -10
  171. package/std/time/instant.yo +4 -4
  172. package/std/time/sleep.yo +1 -0
  173. package/std/url/index.yo +3 -3
  174. package/std/worker.yo +4 -3
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Replace verbose `(&(X)).index(i).*` patterns with the Rust-like
3
+ * `X(i)` indexing syntax that Yo natively supports.
4
+ *
5
+ * Patterns rewritten (most → least common):
6
+ *
7
+ * 1. WRITE: `(&(X)).index(I).* = V;` → `X(I) = V;`
8
+ * 2. READ.X: `(&(X)).index(I).*.PROP` → `X(I).PROP`
9
+ * 3. READ: `(&(X)).index(I).*` → `X(I)`
10
+ * (anywhere `X(I)` would parse the same way — i.e. used as a
11
+ * value, not as a pointer target for another `.*` op.)
12
+ *
13
+ * Patterns deliberately NOT touched:
14
+ *
15
+ * - `ptr := (&(X)).index(I)` — author wanted the pointer; leave
16
+ * it. Rewriting would change the semantics (you'd get `T` not
17
+ * `*(T)`).
18
+ * - `(&(X)).index(I)` followed by anything else (e.g. another
19
+ * method call on the pointer). Conservative — only the three
20
+ * shapes above are touched.
21
+ *
22
+ * For `X`: any balanced expression (handles nesting like
23
+ * `(&(frame.variables))`, `(&(a.b.c))`, etc).
24
+ *
25
+ * Usage:
26
+ * bun run scripts/migrate-index-patterns.ts # dry-run
27
+ * bun run scripts/migrate-index-patterns.ts --write # apply
28
+ */
29
+
30
+ import { readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
31
+ import * as path from "node:path";
32
+
33
+ const ROOTS = ["std", "yo-self", "tests"];
34
+
35
+ interface Stats {
36
+ writes: number;
37
+ readDotProp: number;
38
+ readStandalone: number;
39
+ }
40
+
41
+ /**
42
+ * Match `(&(X))` at `start` and return the index just after the
43
+ * closing `))`, or null if no match. Returns the balanced inner `X`
44
+ * (paren-balanced).
45
+ */
46
+ function matchAmpersandWrap(
47
+ content: string,
48
+ start: number
49
+ ): { x: string; end: number } | null {
50
+ if (content.slice(start, start + 3) !== "(&(") return null;
51
+ let depth = 1;
52
+ let i = start + 3;
53
+ const xStart = i;
54
+ while (i < content.length) {
55
+ const ch = content[i]!;
56
+ if (ch === "(") depth++;
57
+ else if (ch === ")") {
58
+ depth--;
59
+ if (depth === 0) break;
60
+ }
61
+ i++;
62
+ }
63
+ if (i >= content.length) return null;
64
+ const x = content.slice(xStart, i);
65
+ // i points at the closing `)` of the inner. Next char should be
66
+ // the outer closing `)`.
67
+ if (content[i + 1] !== ")") return null;
68
+ return { x, end: i + 2 };
69
+ }
70
+
71
+ /**
72
+ * Match `.index(I)` starting at `start`. Returns the index argument
73
+ * and the position just after the closing `)`.
74
+ */
75
+ function matchIndexCall(
76
+ content: string,
77
+ start: number
78
+ ): { idx: string; end: number } | null {
79
+ if (content.slice(start, start + 7) !== ".index(") return null;
80
+ let depth = 1;
81
+ let i = start + 7;
82
+ const idxStart = i;
83
+ while (i < content.length) {
84
+ const ch = content[i]!;
85
+ if (ch === "(") depth++;
86
+ else if (ch === ")") {
87
+ depth--;
88
+ if (depth === 0) break;
89
+ }
90
+ i++;
91
+ }
92
+ if (i >= content.length) return null;
93
+ const idx = content.slice(idxStart, i);
94
+ return { idx, end: i + 1 };
95
+ }
96
+
97
+ function migrate(content: string): { result: string; stats: Stats } {
98
+ const stats: Stats = { writes: 0, readDotProp: 0, readStandalone: 0 };
99
+ const out: string[] = [];
100
+ let i = 0;
101
+ while (i < content.length) {
102
+ if (content.slice(i, i + 3) !== "(&(") {
103
+ out.push(content[i]!);
104
+ i++;
105
+ continue;
106
+ }
107
+ const wrap = matchAmpersandWrap(content, i);
108
+ if (!wrap) {
109
+ out.push(content[i]!);
110
+ i++;
111
+ continue;
112
+ }
113
+ const idxCall = matchIndexCall(content, wrap.end);
114
+ if (!idxCall) {
115
+ out.push(content[i]!);
116
+ i++;
117
+ continue;
118
+ }
119
+ // Now check what follows .index(I).
120
+ if (content.slice(idxCall.end, idxCall.end + 2) !== ".*") {
121
+ // `(&(X)).index(I)` not followed by `.*` — leave alone (author
122
+ // wanted the raw pointer).
123
+ out.push(content[i]!);
124
+ i++;
125
+ continue;
126
+ }
127
+ // After `.*`, decide which variant.
128
+ const afterStar = idxCall.end + 2;
129
+ const trailing = content.slice(afterStar);
130
+
131
+ // WRITE: `(&(X)).index(I).* = V` — match ` = ` (with whitespace).
132
+ const writeMatch = trailing.match(/^\s*=\s*/);
133
+ // READ.X: `(&(X)).index(I).*.PROP` — match `.`
134
+ const readDotMatch = trailing.match(/^\.([A-Za-z_])/);
135
+
136
+ if (writeMatch) {
137
+ out.push(`${wrap.x}(${idxCall.idx})${writeMatch[0]}`);
138
+ i = afterStar + writeMatch[0].length;
139
+ stats.writes++;
140
+ continue;
141
+ }
142
+ if (readDotMatch) {
143
+ out.push(`${wrap.x}(${idxCall.idx})`);
144
+ // Skip the `.*` but keep the trailing `.` and identifier.
145
+ i = afterStar;
146
+ stats.readDotProp++;
147
+ continue;
148
+ }
149
+ // Standalone read — `.*` is the end. Yo's `X(i)` returns T (same
150
+ // as `.*` of the pointer). Safe to rewrite when the next char
151
+ // is a separator (newline, `,`, `)`, `;`, etc.).
152
+ const nextCh = trailing[0] ?? "";
153
+ if (/[\s,);}\]]/.test(nextCh) || nextCh === "") {
154
+ out.push(`${wrap.x}(${idxCall.idx})`);
155
+ i = afterStar;
156
+ stats.readStandalone++;
157
+ continue;
158
+ }
159
+ // Anything else following `.*` (e.g. another operator like `&+`)
160
+ // — leave alone to be safe.
161
+ out.push(content[i]!);
162
+ i++;
163
+ }
164
+ return { result: out.join(""), stats };
165
+ }
166
+
167
+ function* walk(dir: string): Generator<string> {
168
+ for (const entry of readdirSync(dir)) {
169
+ if (entry.startsWith(".")) continue;
170
+ const fullPath = path.join(dir, entry);
171
+ const st = statSync(fullPath);
172
+ if (st.isDirectory()) {
173
+ yield* walk(fullPath);
174
+ } else if (st.isFile() && entry.endsWith(".yo")) {
175
+ yield fullPath;
176
+ }
177
+ }
178
+ }
179
+
180
+ function main(): void {
181
+ const write = process.argv.includes("--write");
182
+ const totals: Stats = { writes: 0, readDotProp: 0, readStandalone: 0 };
183
+ let filesTouched = 0;
184
+ for (const root of ROOTS) {
185
+ const absRoot = path.resolve(root);
186
+ for (const filePath of walk(absRoot)) {
187
+ const content = readFileSync(filePath, "utf-8");
188
+ const { result, stats } = migrate(content);
189
+ if (result === content) continue;
190
+ filesTouched++;
191
+ totals.writes += stats.writes;
192
+ totals.readDotProp += stats.readDotProp;
193
+ totals.readStandalone += stats.readStandalone;
194
+ console.log(
195
+ `${write ? "migrated" : "would migrate"}: ${path.relative(
196
+ process.cwd(),
197
+ filePath
198
+ )} (writes=${stats.writes}, read.prop=${stats.readDotProp}, read=${stats.readStandalone})`
199
+ );
200
+ if (write) writeFileSync(filePath, result, "utf-8");
201
+ }
202
+ }
203
+ console.log(
204
+ `\n${write ? "Migrated" : "Would migrate"} ${filesTouched} file(s): ` +
205
+ `${totals.writes} writes, ${totals.readDotProp} read-then-prop, ${totals.readStandalone} standalone reads.`
206
+ );
207
+ if (!write) console.log("(dry-run — pass --write to apply)");
208
+ }
209
+
210
+ main();
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Migrate the Index trait's `index` method from `(self : *(Self))`
3
+ * to `(inout(self) : Self)`. Same shape as the earlier Iterator
4
+ * migration.
5
+ *
6
+ * Walks every `index : (fn(self : *(Self), ...) -> *(Self.Output))
7
+ * ({ body })` impl and:
8
+ * 1. Rewrites the signature.
9
+ * 2. Replaces `self.*` with `self` inside the method body only.
10
+ *
11
+ * Usage:
12
+ * bun run scripts/migrate-index-trait.ts # dry-run
13
+ * bun run scripts/migrate-index-trait.ts --write # apply
14
+ */
15
+
16
+ import { readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
17
+ import * as path from "node:path";
18
+
19
+ const ROOTS = ["std", "yo-self", "tests"];
20
+
21
+ interface Stats {
22
+ sigs: number;
23
+ derefs: number;
24
+ }
25
+
26
+ function migrate(content: string): { result: string; stats: Stats } {
27
+ const stats: Stats = { sigs: 0, derefs: 0 };
28
+ const out: string[] = [];
29
+ let i = 0;
30
+ // Index sigs always have a trailing comma after `*(Self)` (because
31
+ // there's always an `idx : ...` parameter): `index : (fn(self : *(Self),`
32
+ const sigStart = /index : \(fn\(self : \*\(Self\),/g;
33
+
34
+ while (i < content.length) {
35
+ sigStart.lastIndex = i;
36
+ const m = sigStart.exec(content);
37
+ if (!m) {
38
+ out.push(content.slice(i));
39
+ break;
40
+ }
41
+ const startIdx = m.index;
42
+ out.push(content.slice(i, startIdx));
43
+ out.push("index : (fn(inout(self) : Self,");
44
+ stats.sigs++;
45
+ let j = startIdx + m[0].length;
46
+
47
+ // The regex consumed `index : (fn(self : *(Self),` which opened
48
+ // two more parens than it closed (the outer `(` after `:` and
49
+ // the `(` after `fn`). We need to walk to where those *outer*
50
+ // parens close — past `idx : ..., ...) -> *(Self.Output))`.
51
+ let depth = 2;
52
+ while (j < content.length) {
53
+ const ch = content[j]!;
54
+ if (ch === "(") depth++;
55
+ else if (ch === ")") {
56
+ depth--;
57
+ if (depth === 0) {
58
+ j++;
59
+ break;
60
+ }
61
+ }
62
+ j++;
63
+ }
64
+ out.push(content.slice(startIdx + m[0].length, j));
65
+
66
+ // Walk past whitespace then the body `(...)`.
67
+ while (j < content.length && /\s/.test(content[j]!)) {
68
+ out.push(content[j]!);
69
+ j++;
70
+ }
71
+ if (content[j] !== "(") {
72
+ i = j;
73
+ continue;
74
+ }
75
+ const bodyStart = j;
76
+ let bodyDepth = 0;
77
+ while (j < content.length) {
78
+ const ch = content[j]!;
79
+ if (ch === "(") bodyDepth++;
80
+ else if (ch === ")") {
81
+ bodyDepth--;
82
+ if (bodyDepth === 0) {
83
+ j++;
84
+ break;
85
+ }
86
+ }
87
+ j++;
88
+ }
89
+ const body = content.slice(bodyStart, j);
90
+ const newBody = body.replace(/self\.\*/g, () => {
91
+ stats.derefs++;
92
+ return "self";
93
+ });
94
+ out.push(newBody);
95
+ i = j;
96
+ }
97
+ return { result: out.join(""), stats };
98
+ }
99
+
100
+ function* walk(dir: string): Generator<string> {
101
+ for (const entry of readdirSync(dir)) {
102
+ if (entry.startsWith(".")) continue;
103
+ const fullPath = path.join(dir, entry);
104
+ const st = statSync(fullPath);
105
+ if (st.isDirectory()) {
106
+ yield* walk(fullPath);
107
+ } else if (st.isFile() && entry.endsWith(".yo")) {
108
+ yield fullPath;
109
+ }
110
+ }
111
+ }
112
+
113
+ function main(): void {
114
+ const write = process.argv.includes("--write");
115
+ let totalSigs = 0;
116
+ let totalDerefs = 0;
117
+ let filesTouched = 0;
118
+ for (const root of ROOTS) {
119
+ const absRoot = path.resolve(root);
120
+ for (const filePath of walk(absRoot)) {
121
+ const content = readFileSync(filePath, "utf-8");
122
+ const { result, stats } = migrate(content);
123
+ if (result === content) continue;
124
+ filesTouched++;
125
+ totalSigs += stats.sigs;
126
+ totalDerefs += stats.derefs;
127
+ console.log(
128
+ `${write ? "migrated" : "would migrate"}: ${path.relative(
129
+ process.cwd(),
130
+ filePath
131
+ )} (sigs=${stats.sigs}, derefs=${stats.derefs})`
132
+ );
133
+ if (write) writeFileSync(filePath, result, "utf-8");
134
+ }
135
+ }
136
+ console.log(
137
+ `\n${write ? "Migrated" : "Would migrate"} ${totalSigs} signatures + ${totalDerefs} self.* rewrites across ${filesTouched} file(s).`
138
+ );
139
+ if (!write) console.log("(dry-run — pass --write to apply)");
140
+ }
141
+
142
+ main();
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Migrate the Iterator trait's `next` method from `(self : *(Self))`
3
+ * to `(inout(self) : Self)`. Mirrors what was done for Hash, Clone,
4
+ * and ToString.
5
+ *
6
+ * The trait declaration in `std/prelude.yo` is updated manually
7
+ * before running this script. The script then walks every
8
+ * `next : (fn(self : *(Self)) -> RETURN_TYPE)({ ...body... })` impl
9
+ * and:
10
+ * 1. Replaces the signature with `next : (fn(inout(self) : Self) ->
11
+ * RETURN_TYPE)`.
12
+ * 2. Replaces `self.*` with `self` *inside the method body only*.
13
+ *
14
+ * The `RETURN_TYPE` is arbitrary balanced text — typically
15
+ * `Option(Self.Item)` or `Option(*(T))` etc. — and is left
16
+ * verbatim. The script intentionally does NOT touch other
17
+ * `(self : *(Self))` patterns; the migrate-self-ptr.ts driver
18
+ * handles those.
19
+ *
20
+ * Usage:
21
+ * bun run scripts/migrate-iterator.ts # dry-run
22
+ * bun run scripts/migrate-iterator.ts --write # apply
23
+ */
24
+
25
+ import { readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
26
+ import * as path from "node:path";
27
+
28
+ const ROOTS = ["std", "yo-self", "tests"];
29
+
30
+ interface Stats {
31
+ sigs: number;
32
+ derefs: number;
33
+ }
34
+
35
+ function migrate(content: string): { result: string; stats: Stats } {
36
+ const stats: Stats = { sigs: 0, derefs: 0 };
37
+ const out: string[] = [];
38
+ let i = 0;
39
+ const sigStart = /next : \(fn\(self : \*\(Self\)\)/g;
40
+
41
+ while (i < content.length) {
42
+ sigStart.lastIndex = i;
43
+ const m = sigStart.exec(content);
44
+ if (!m) {
45
+ out.push(content.slice(i));
46
+ break;
47
+ }
48
+ const startIdx = m.index;
49
+ out.push(content.slice(i, startIdx));
50
+
51
+ // Walk past ` -> RETURN_TYPE)` — the closing `)` of the `(fn(...) -> ...)` form.
52
+ // Skip 1 layer of nesting for the return type.
53
+ const newSig = "next : (fn(inout(self) : Self)";
54
+ out.push(newSig);
55
+ let j = startIdx + m[0].length;
56
+ let depth = 0;
57
+ while (j < content.length) {
58
+ const ch = content[j]!;
59
+ if (ch === "(") depth++;
60
+ else if (ch === ")") {
61
+ if (depth === 0) {
62
+ j++;
63
+ break;
64
+ }
65
+ depth--;
66
+ }
67
+ j++;
68
+ }
69
+ // Emit ` -> RETURN_TYPE)` unchanged.
70
+ out.push(content.slice(startIdx + m[0].length, j));
71
+ stats.sigs++;
72
+
73
+ // Walk past whitespace then expect `(` for the body.
74
+ while (j < content.length && /\s/.test(content[j]!)) {
75
+ out.push(content[j]!);
76
+ j++;
77
+ }
78
+ if (content[j] !== "(") {
79
+ i = j;
80
+ continue;
81
+ }
82
+ // Walk the body, balanced parens, rewriting `self.*` → `self`.
83
+ const bodyStart = j;
84
+ let bodyDepth = 0;
85
+ while (j < content.length) {
86
+ const ch = content[j]!;
87
+ if (ch === "(") bodyDepth++;
88
+ else if (ch === ")") {
89
+ bodyDepth--;
90
+ if (bodyDepth === 0) {
91
+ j++;
92
+ break;
93
+ }
94
+ }
95
+ j++;
96
+ }
97
+ const body = content.slice(bodyStart, j);
98
+ const newBody = body.replace(/self\.\*/g, () => {
99
+ stats.derefs++;
100
+ return "self";
101
+ });
102
+ out.push(newBody);
103
+ i = j;
104
+ }
105
+ return { result: out.join(""), stats };
106
+ }
107
+
108
+ function* walk(dir: string): Generator<string> {
109
+ for (const entry of readdirSync(dir)) {
110
+ if (entry.startsWith(".")) continue;
111
+ const fullPath = path.join(dir, entry);
112
+ const st = statSync(fullPath);
113
+ if (st.isDirectory()) {
114
+ yield* walk(fullPath);
115
+ } else if (st.isFile() && entry.endsWith(".yo")) {
116
+ yield fullPath;
117
+ }
118
+ }
119
+ }
120
+
121
+ function main(): void {
122
+ const write = process.argv.includes("--write");
123
+ let totalSigs = 0;
124
+ let totalDerefs = 0;
125
+ let filesTouched = 0;
126
+ for (const root of ROOTS) {
127
+ const absRoot = path.resolve(root);
128
+ for (const filePath of walk(absRoot)) {
129
+ const content = readFileSync(filePath, "utf-8");
130
+ const { result, stats } = migrate(content);
131
+ if (result === content) continue;
132
+ filesTouched++;
133
+ totalSigs += stats.sigs;
134
+ totalDerefs += stats.derefs;
135
+ console.log(
136
+ `${write ? "migrated" : "would migrate"}: ${path.relative(
137
+ process.cwd(),
138
+ filePath
139
+ )} (sigs=${stats.sigs}, derefs=${stats.derefs})`
140
+ );
141
+ if (write) writeFileSync(filePath, result, "utf-8");
142
+ }
143
+ }
144
+ console.log(
145
+ `\n${write ? "Migrated" : "Would migrate"} ${totalSigs} signatures + ${totalDerefs} self.* rewrites across ${filesTouched} file(s).`
146
+ );
147
+ if (!write) console.log("(dry-run — pass --write to apply)");
148
+ }
149
+
150
+ main();