@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.
- package/.github/skills/yo-async-effects/SKILL.md +3 -3
- package/.github/skills/yo-async-effects/async-effects-recipes.md +19 -11
- package/.github/skills/yo-core-patterns/core-patterns-cheatsheet.md +33 -13
- package/.github/skills/yo-project-workflow/workflow-cheatsheet.md +1 -1
- package/.github/skills/yo-syntax/syntax-cheatsheet.md +59 -21
- package/README.md +4 -3
- package/out/cjs/index.cjs +771 -676
- package/out/cjs/yo-cli.cjs +1003 -898
- package/out/cjs/yo-lsp.cjs +834 -739
- package/out/esm/index.mjs +716 -621
- package/out/types/src/codegen/exprs/async.d.ts +2 -0
- package/out/types/src/codegen/exprs/await.d.ts +1 -0
- package/out/types/src/codegen/exprs/closures.d.ts +4 -0
- package/out/types/src/codegen/functions/context.d.ts +6 -0
- package/out/types/src/env.d.ts +2 -0
- package/out/types/src/evaluator/builtins/pragma.d.ts +9 -0
- package/out/types/src/evaluator/builtins/unsafe.d.ts +8 -0
- package/out/types/src/evaluator/context.d.ts +2 -0
- package/out/types/src/evaluator/index.d.ts +1 -1
- package/out/types/src/evaluator/memory-safety.d.ts +14 -0
- package/out/types/src/evaluator/types/flowability.d.ts +6 -0
- package/out/types/src/expr-traversal.d.ts +1 -0
- package/out/types/src/expr.d.ts +4 -1
- package/out/types/src/public-safe-report.d.ts +19 -0
- package/out/types/src/tests/comptime-ref-gate.test.d.ts +1 -0
- package/out/types/src/tests/pragma-validation.test.d.ts +1 -0
- package/out/types/src/tests/public-safe-report.test.d.ts +1 -0
- package/out/types/src/tests/type-representation-pointer.test.d.ts +1 -0
- package/out/types/src/tests/unsafe-gate.test.d.ts +1 -0
- package/out/types/src/tests/unsafe-report-classify.test.d.ts +1 -0
- package/out/types/src/types/definitions.d.ts +2 -0
- package/out/types/src/types/utils.d.ts +4 -0
- package/out/types/src/unsafe-report.d.ts +29 -0
- package/out/types/src/value.d.ts +1 -0
- package/out/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/scripts/add-pragma-for-pointer-decls.ts +134 -0
- package/scripts/add-pragma.ts +58 -0
- package/scripts/migrate-amp-method-calls.ts +186 -0
- package/scripts/migrate-clone-calls.ts +93 -0
- package/scripts/migrate-get-unwrap.ts +166 -0
- package/scripts/migrate-index-patterns.ts +210 -0
- package/scripts/migrate-index-trait.ts +142 -0
- package/scripts/migrate-iterator.ts +150 -0
- package/scripts/migrate-self-ptr.ts +220 -0
- package/scripts/migrate-skip-pragmas.ts +109 -0
- package/scripts/migrate-tostring.ts +134 -0
- package/scripts/trim-pragma.ts +130 -0
- package/scripts/wrap-extern-calls.ts +161 -0
- package/std/alg/hash.yo +3 -2
- package/std/allocator.yo +6 -5
- package/std/async.yo +2 -2
- package/std/collections/array_list.yo +59 -40
- package/std/collections/btree_map.yo +19 -18
- package/std/collections/deque.yo +9 -8
- package/std/collections/hash_map.yo +101 -13
- package/std/collections/hash_set.yo +5 -4
- package/std/collections/linked_list.yo +39 -4
- package/std/collections/ordered_map.yo +3 -3
- package/std/collections/priority_queue.yo +14 -13
- package/std/crypto/md5.yo +2 -1
- package/std/crypto/random.yo +16 -15
- package/std/crypto/sha256.yo +2 -1
- package/std/encoding/base64.yo +14 -14
- package/std/encoding/hex.yo +3 -3
- package/std/encoding/json.yo +59 -10
- package/std/encoding/punycode.yo +24 -23
- package/std/encoding/toml.yo +4 -3
- package/std/encoding/utf16.yo +2 -2
- package/std/env.yo +43 -28
- package/std/error.yo +6 -6
- package/std/fmt/display.yo +2 -2
- package/std/fmt/index.yo +6 -5
- package/std/fmt/to_string.yo +39 -38
- package/std/fmt/writer.yo +9 -8
- package/std/fs/dir.yo +34 -33
- package/std/fs/file.yo +52 -51
- package/std/fs/metadata.yo +10 -9
- package/std/fs/temp.yo +24 -13
- package/std/fs/walker.yo +10 -9
- package/std/gc.yo +1 -0
- package/std/glob.yo +7 -7
- package/std/http/client.yo +15 -14
- package/std/http/http.yo +6 -6
- package/std/http/index.yo +1 -1
- package/std/imm/list.yo +33 -0
- package/std/imm/map.yo +2 -1
- package/std/imm/set.yo +1 -0
- package/std/imm/sorted_map.yo +1 -0
- package/std/imm/sorted_set.yo +1 -0
- package/std/imm/string.yo +27 -23
- package/std/imm/vec.yo +18 -2
- package/std/io/reader.yo +2 -1
- package/std/io/writer.yo +3 -2
- package/std/libc/assert.yo +1 -0
- package/std/libc/ctype.yo +1 -0
- package/std/libc/dirent.yo +1 -0
- package/std/libc/errno.yo +1 -0
- package/std/libc/fcntl.yo +1 -0
- package/std/libc/float.yo +1 -0
- package/std/libc/limits.yo +1 -0
- package/std/libc/math.yo +1 -0
- package/std/libc/signal.yo +1 -0
- package/std/libc/stdatomic.yo +1 -0
- package/std/libc/stdint.yo +1 -0
- package/std/libc/stdio.yo +1 -0
- package/std/libc/stdlib.yo +1 -0
- package/std/libc/string.yo +1 -0
- package/std/libc/sys/stat.yo +1 -0
- package/std/libc/time.yo +1 -0
- package/std/libc/unistd.yo +1 -0
- package/std/libc/wctype.yo +1 -0
- package/std/libc/windows.yo +2 -0
- package/std/log.yo +7 -6
- package/std/net/addr.yo +5 -4
- package/std/net/dns.yo +7 -6
- package/std/net/errors.yo +8 -8
- package/std/net/tcp.yo +19 -18
- package/std/net/udp.yo +13 -12
- package/std/os/signal.yo +3 -3
- package/std/path.yo +1 -0
- package/std/prelude.yo +353 -182
- package/std/process/command.yo +40 -23
- package/std/process/index.yo +2 -1
- package/std/regex/compiler.yo +10 -9
- package/std/regex/index.yo +41 -41
- package/std/regex/match.yo +2 -2
- package/std/regex/parser.yo +21 -21
- package/std/regex/vm.yo +42 -41
- package/std/string/string.yo +95 -40
- package/std/string/string_builder.yo +9 -9
- package/std/string/unicode.yo +50 -49
- package/std/sync/channel.yo +2 -1
- package/std/sync/cond.yo +5 -4
- package/std/sync/mutex.yo +4 -3
- package/std/sys/advise.yo +1 -0
- package/std/sys/bufio/buf_reader.yo +17 -16
- package/std/sys/bufio/buf_writer.yo +10 -9
- package/std/sys/clock.yo +1 -0
- package/std/sys/copy.yo +1 -0
- package/std/sys/dir.yo +10 -9
- package/std/sys/dns.yo +6 -5
- package/std/sys/errors.yo +11 -11
- package/std/sys/events.yo +1 -0
- package/std/sys/externs.yo +38 -37
- package/std/sys/file.yo +17 -16
- package/std/sys/future.yo +4 -3
- package/std/sys/iov.yo +1 -0
- package/std/sys/mmap.yo +1 -0
- package/std/sys/path.yo +1 -0
- package/std/sys/perm.yo +2 -1
- package/std/sys/pipe.yo +1 -0
- package/std/sys/process.yo +5 -4
- package/std/sys/signal.yo +1 -0
- package/std/sys/socketpair.yo +1 -0
- package/std/sys/sockinfo.yo +1 -0
- package/std/sys/statfs.yo +2 -1
- package/std/sys/statx.yo +1 -0
- package/std/sys/sysinfo.yo +1 -0
- package/std/sys/tcp.yo +15 -14
- package/std/sys/temp.yo +1 -0
- package/std/sys/time.yo +2 -1
- package/std/sys/timer.yo +6 -6
- package/std/sys/tty.yo +2 -1
- package/std/sys/udp.yo +13 -12
- package/std/sys/unix.yo +12 -11
- package/std/testing/bench.yo +4 -3
- package/std/thread.yo +7 -6
- package/std/time/datetime.yo +18 -15
- package/std/time/duration.yo +11 -10
- package/std/time/instant.yo +4 -4
- package/std/time/sleep.yo +1 -0
- package/std/url/index.yo +3 -3
- 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();
|