@shd101wyy/yo 0.1.21 → 0.1.22

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.
@@ -0,0 +1,303 @@
1
+ //! High-level child-process spawning with builder-style configuration.
2
+ //!
3
+ //! Wraps `std/sys/process` with a fluent API for constructing argv, capturing
4
+ //! stdout/stderr through pipes, and waiting for the child to exit.
5
+ //!
6
+ //! # Example
7
+ //!
8
+ //! ```rust
9
+ //! { Command } :: import "std/process/command";
10
+ //! { Exception, AnyError } :: import "std/error";
11
+ //!
12
+ //! main :: (fn(using(io : IO)) -> unit)({
13
+ //! given(exn) := Exception(
14
+ //! throw : (fn(forall(T : Type), error: AnyError) -> T)(
15
+ //! { println(error); panic("Exception"); }
16
+ //! )
17
+ //! );
18
+ //!
19
+ //! cmd := Command.new(`echo`);
20
+ //! cmd.arg(`hello`);
21
+ //! cmd.arg(`world`);
22
+ //! out := io.await(cmd.output());
23
+ //! assert(out.status.success(), "echo should succeed");
24
+ //! });
25
+ //! ```
26
+
27
+ open import "../collections/array_list";
28
+ open import "../string";
29
+ { Exception } :: import "../error";
30
+ { IOError } :: import "../sys/errors";
31
+ IO_process :: import "../sys/process";
32
+ IO_file :: import "../sys/file";
33
+ IO_pipe :: import "../sys/pipe";
34
+ { GlobalAllocator } :: import "../allocator";
35
+ { malloc, free } :: GlobalAllocator;
36
+
37
+ // ============================================================================
38
+ // ExitStatus
39
+ // ============================================================================
40
+
41
+ /// The result of a finished child process.
42
+ ///
43
+ /// Holds the raw waitpid status code along with helpers to extract the exit
44
+ /// code and termination signal.
45
+ ExitStatus :: struct(
46
+ /// Encoded waitpid status. Use `code()` / `signal()` helpers instead of
47
+ /// reading this directly when possible.
48
+ raw : i32
49
+ );
50
+
51
+ impl(ExitStatus,
52
+ /// Returns the process exit code (0..255 on Unix). Returns 0 when the
53
+ /// process was terminated by a signal.
54
+ code : (fn(self: Self) -> i32)(
55
+ IO_process.exit_status(self.raw)
56
+ ),
57
+
58
+ /// Returns the signal number that terminated the process, or 0 if the
59
+ /// process exited normally.
60
+ signal : (fn(self: Self) -> i32)(
61
+ IO_process.term_signal(self.raw)
62
+ ),
63
+
64
+ /// Returns true if the child exited with status code 0.
65
+ success : (fn(self: Self) -> bool)(
66
+ ((IO_process.exit_status(self.raw) == i32(0)) && (IO_process.term_signal(self.raw) == i32(0)))
67
+ )
68
+ );
69
+
70
+ // ============================================================================
71
+ // Output
72
+ // ============================================================================
73
+
74
+ /// Captured output of a child process.
75
+ ///
76
+ /// Returned by `Command.output`. Holds the exit status plus the bytes captured
77
+ /// from the child's stdout and stderr.
78
+ Output :: object(
79
+ status : ExitStatus,
80
+ stdout : ArrayList(u8),
81
+ stderr : ArrayList(u8)
82
+ );
83
+
84
+ // ============================================================================
85
+ // Command
86
+ // ============================================================================
87
+
88
+ /// Builder for a child-process invocation.
89
+ ///
90
+ /// Construct with `Command.new(program)`, then chain mutating builder methods
91
+ /// (`arg`, `args`, `env_clear`) before invoking `status()` or `output()`.
92
+ Command :: object(
93
+ _program : String,
94
+ _args : ArrayList(String),
95
+ _stdin_fd : i32,
96
+ _stdout_fd : i32,
97
+ _stderr_fd : i32
98
+ );
99
+
100
+ impl(Command,
101
+ /// Create a new `Command` invoking `program`. The program name is also used
102
+ /// as `argv[0]` unless a different first arg is pushed manually before
103
+ /// invocation.
104
+ new : (fn(program: String) -> Self)(
105
+ Self(
106
+ _program: program,
107
+ _args: ArrayList(String).new(),
108
+ _stdin_fd: i32(-1),
109
+ _stdout_fd: i32(-1),
110
+ _stderr_fd: i32(-1)
111
+ )
112
+ ),
113
+
114
+ /// Append a single argument to the argv list.
115
+ arg : (fn(self: *(Self), a: String) -> unit)({
116
+ self._args.push(a);
117
+ ()
118
+ }),
119
+
120
+ /// Append multiple arguments to the argv list.
121
+ args : (fn(self: *(Self), more: ArrayList(String)) -> unit)({
122
+ n := more.len();
123
+ i := usize(0);
124
+ while runtime((i < n)), {
125
+ match(more.get(i),
126
+ .Some(s) => { self._args.push(s); },
127
+ .None => ()
128
+ );
129
+ i = (i + usize(1));
130
+ };
131
+ })
132
+ );
133
+
134
+ // ============================================================================
135
+ // Internal helpers
136
+ // ============================================================================
137
+
138
+ // Convert each entry of `(program + args)` into an owned NUL-terminated
139
+ // `ArrayList(u8)`. The returned list owns the C-string storage; callers
140
+ // extract the raw `*(u8)` pointers separately.
141
+ _build_cstr_storage :: (fn(self: *(Command)) -> ArrayList(ArrayList(u8)))({
142
+ storage := ArrayList(ArrayList(u8)).new();
143
+ storage.push(self._program.to_cstr());
144
+ n := self._args.len();
145
+ i := usize(0);
146
+ while runtime((i < n)), {
147
+ match(self._args.get(i),
148
+ .Some(s) => { storage.push(s.to_cstr()); },
149
+ .None => ()
150
+ );
151
+ i = (i + usize(1));
152
+ };
153
+ return storage;
154
+ });
155
+
156
+ // Spawn the child with the given inherited/redirected fds and return the pid.
157
+ // Builds argv as a fixed `?*(u8)` array on the heap. Caller is responsible for
158
+ // freeing `argv_buf` AND keeping `storage` alive until after `__yo_async_spawn_start`
159
+ // returns the pid (since the kernel reads argv synchronously inside the syscall).
160
+ _spawn_with_fds :: (fn(
161
+ self: *(Command),
162
+ stdin_fd: i32,
163
+ stdout_fd: i32,
164
+ stderr_fd: i32,
165
+ using(io : IO, exn : Exception)
166
+ ) -> Impl(Future(i32, IO, Exception)))({
167
+ io.async((using(io, exn)) => {
168
+ storage := _build_cstr_storage(self);
169
+ argc := storage.len();
170
+
171
+ // Allocate argv buffer: argc entries + 1 null terminator.
172
+ argv_bytes := (sizeof(?*(u8)) * (argc + usize(1)));
173
+ argv_raw := malloc(argv_bytes).unwrap();
174
+ argv_buf := *(?*(u8))(argv_raw);
175
+
176
+ // Fill argv pointers from each owning ArrayList(u8) in storage.
177
+ j := usize(0);
178
+ while runtime((j < argc)), {
179
+ match(storage.get(j),
180
+ .Some(cs) => match(cs.ptr(),
181
+ .Some(p) => { (argv_buf &+ j).* = .Some(p); },
182
+ .None => { (argv_buf &+ j).* = .None; }
183
+ ),
184
+ .None => { (argv_buf &+ j).* = .None; }
185
+ );
186
+ j = (j + usize(1));
187
+ };
188
+ (argv_buf &+ argc).* = .None;
189
+
190
+ // Resolve program path C string (always present — _build_cstr_storage pushes program first).
191
+ program_cstr := (argv_buf &+ usize(0)).*.unwrap();
192
+
193
+ pid := io.await(IO_process.spawn(program_cstr, argv_buf, .None, stdin_fd, stdout_fd, stderr_fd));
194
+
195
+ // Free argv buffer (storage is dropped naturally at scope end).
196
+ free(.Some(argv_raw));
197
+
198
+ IOError.check(pid)
199
+ })
200
+ });
201
+
202
+ // Read all available bytes from `fd` into a freshly returned ArrayList(u8)
203
+ // until EOF (read returns 0).
204
+ _drain_fd :: (fn(fd: i32, using(io : IO, exn : Exception)) -> Impl(Future(ArrayList(u8), IO, Exception)))({
205
+ io.async((using(io, exn)) => {
206
+ buf_size := usize(4096);
207
+ buf := *(u8)(malloc(buf_size).unwrap());
208
+ out := ArrayList(u8).new();
209
+ while runtime(true), {
210
+ n := io.await(IO_file.read(fd, buf, u32(buf_size), u64(0)));
211
+ cond(
212
+ (n < i32(0)) => {
213
+ free(.Some(*(void)(buf)));
214
+ exn.throw(dyn IOError.from_errno((i32(0) - n)));
215
+ },
216
+ (n == i32(0)) => break,
217
+ true => {
218
+ k := usize(0);
219
+ while runtime((k < usize(n))), {
220
+ out.push((buf &+ k).*);
221
+ k = (k + usize(1));
222
+ };
223
+ }
224
+ );
225
+ };
226
+ free(.Some(*(void)(buf)));
227
+ out
228
+ })
229
+ });
230
+
231
+ // ============================================================================
232
+ // Public Command methods (status / output)
233
+ // ============================================================================
234
+
235
+ impl(Command,
236
+ /// Spawn the child with stdio inherited from the parent, wait for it to
237
+ /// exit, and return its `ExitStatus`.
238
+ status : (fn(self: *(Self), using(io : IO)) -> Impl(Future(ExitStatus, IO, Exception)))({
239
+ io.async((using(io, exn)) => {
240
+ pid := io.await(_spawn_with_fds(self, i32(-1), i32(-1), i32(-1)));
241
+ raw := io.await(IO_process.waitpid(pid, i32(0)));
242
+ IOError.check(raw);
243
+ ExitStatus(raw: raw)
244
+ })
245
+ }),
246
+
247
+ /// Spawn the child with stdout and stderr captured through pipes. Waits for
248
+ /// the child to exit and returns the exit status plus the captured bytes.
249
+ output : (fn(self: *(Self), using(io : IO)) -> Impl(Future(Output, IO, Exception)))({
250
+ io.async((using(io, exn)) => {
251
+ // Create stdout and stderr pipes.
252
+ out_fd_buf := MaybeUninit(Array(i32, usize(2))).new();
253
+ out_fd := *(i32)(out_fd_buf.as_ptr());
254
+ pr1 := IO_pipe.pipe(out_fd);
255
+ cond(
256
+ (pr1 < i32(0)) => exn.throw(dyn IOError.from_errno((i32(0) - pr1))),
257
+ true => ()
258
+ );
259
+ out_read := (out_fd &+ usize(0)).*;
260
+ out_write := (out_fd &+ usize(1)).*;
261
+
262
+ err_fd_buf := MaybeUninit(Array(i32, usize(2))).new();
263
+ err_fd := *(i32)(err_fd_buf.as_ptr());
264
+ pr2 := IO_pipe.pipe(err_fd);
265
+ cond(
266
+ (pr2 < i32(0)) => {
267
+ io.await(IO_file.close(out_read));
268
+ io.await(IO_file.close(out_write));
269
+ exn.throw(dyn IOError.from_errno((i32(0) - pr2)));
270
+ },
271
+ true => ()
272
+ );
273
+ err_read := (err_fd &+ usize(0)).*;
274
+ err_write := (err_fd &+ usize(1)).*;
275
+
276
+ // Spawn child with the write ends of both pipes.
277
+ pid := io.await(_spawn_with_fds(self, i32(-1), out_write, err_write));
278
+
279
+ // Close write ends in parent so reads see EOF when child exits.
280
+ io.await(IO_file.close(out_write));
281
+ io.await(IO_file.close(err_write));
282
+
283
+ // Drain both pipes sequentially.
284
+ stdout_buf := io.await(_drain_fd(out_read));
285
+ stderr_buf := io.await(_drain_fd(err_read));
286
+
287
+ io.await(IO_file.close(out_read));
288
+ io.await(IO_file.close(err_read));
289
+
290
+ raw := io.await(IO_process.waitpid(pid, i32(0)));
291
+ IOError.check(raw);
292
+ Output(
293
+ status: ExitStatus(raw: raw),
294
+ stdout: stdout_buf,
295
+ stderr: stderr_buf
296
+ )
297
+ })
298
+ })
299
+ );
300
+
301
+ export
302
+ Command, ExitStatus, Output
303
+ ;
@@ -0,0 +1,45 @@
1
+ //! Process information: platform/arch detection, child-process spawning, and exit.
2
+ //!
3
+ //! Environment variables, command-line arguments, and current working
4
+ //! directory live in `std/env`.
5
+
6
+ /// Current target platform as a compile-time string.
7
+ /// One of: "linux", "macos", "windows", "freebsd", "emscripten", "wasi".
8
+ platform :: __yo_process_platform();
9
+ export platform;
10
+
11
+ /// Platform constants for compile-time platform comparisons.
12
+ Platform :: {
13
+ Linux : "linux",
14
+ Macos : "macos",
15
+ Windows : "windows",
16
+ FreeBSD : "freebsd",
17
+ Emscripten : "emscripten",
18
+ Wasi : "wasi"
19
+ };
20
+ export Platform;
21
+
22
+ /// Current target architecture as a compile-time string.
23
+ /// One of: "x86_64", "aarch64", "x86", "arm", "wasm32".
24
+ arch :: __yo_process_arch();
25
+ export arch;
26
+
27
+ /// Architecture constants for compile-time architecture comparisons.
28
+ Arch :: {
29
+ X86_64 : "x86_64",
30
+ Aarch64 : "aarch64",
31
+ X86 : "x86",
32
+ Arm : "arm",
33
+ Wasm32 : "wasm32"
34
+ };
35
+ export Arch;
36
+
37
+ /// Exit the process with the given status code.
38
+ exit :: (fn(code : usize) -> unit) {
39
+ { exit : _exit } :: import "../libc/stdlib";
40
+ _exit(int(code));
41
+ };
42
+ export exit;
43
+
44
+ _command :: import "./command.yo";
45
+ export ...(_command);
@@ -2,8 +2,10 @@
2
2
 
3
3
  _rune :: import "./rune.yo";
4
4
  _string :: import "./string.yo";
5
+ _string_builder :: import "./string_builder.yo";
5
6
 
6
7
  export
7
8
  ...(_rune),
8
- ...(_string)
9
+ ...(_string),
10
+ ...(_string_builder)
9
11
  ;
@@ -1558,6 +1558,226 @@ impl(String,
1558
1558
  )
1559
1559
  );
1560
1560
 
1561
+ /// === String utility methods ===
1562
+
1563
+ /**
1564
+ * Line iterator for `String` — yields one line at a time (split on `\n`).
1565
+ * The trailing newline is not included in each yielded line.
1566
+ * Used by `lines()`.
1567
+ */
1568
+ StringLines :: struct(
1569
+ _string : String,
1570
+ _byte_index : usize
1571
+ );
1572
+
1573
+ impl(StringLines, Iterator(
1574
+ Item : String,
1575
+ next : (fn(self : *(Self)) -> Option(String))(
1576
+ match(self._string._bytes,
1577
+ .None => .None,
1578
+ .Some(al) => {
1579
+ total := al.len();
1580
+ cond(
1581
+ (self._byte_index >= total) => .None,
1582
+ true => {
1583
+ start := self._byte_index;
1584
+ i := start;
1585
+ while ((i < total)),
1586
+ (i = (i + usize(1))),
1587
+ {
1588
+ byte_opt := al.get(i);
1589
+ match(byte_opt,
1590
+ .Some(b) => {
1591
+ cond(
1592
+ (b == u8(10)) => { break; },
1593
+ true => ()
1594
+ );
1595
+ },
1596
+ .None => ()
1597
+ );
1598
+ };
1599
+ line_buf := ArrayList(u8).with_capacity((i - start));
1600
+ j := start;
1601
+ while ((j < i)),
1602
+ (j = (j + usize(1))),
1603
+ {
1604
+ byte_opt := al.get(j);
1605
+ match(byte_opt,
1606
+ .Some(b) => { line_buf.push(b); },
1607
+ .None => ()
1608
+ );
1609
+ };
1610
+ self._byte_index = cond(
1611
+ (i < total) => (i + usize(1)),
1612
+ true => total
1613
+ );
1614
+ cond(
1615
+ (line_buf.len() == usize(0)) => .Some(String(_bytes: .None)),
1616
+ true => .Some(String(_bytes: .Some(line_buf)))
1617
+ )
1618
+ }
1619
+ )
1620
+ }
1621
+ )
1622
+ )
1623
+ ));
1624
+
1625
+ impl(String,
1626
+ /**
1627
+ * Returns a line iterator over the string.
1628
+ * Each call to `next()` yields the next line (without the trailing `\n`).
1629
+ *
1630
+ * ## Example
1631
+ * ```rust
1632
+ * s := `hello\nworld`;
1633
+ * iter := s.lines();
1634
+ * assert(iter.next() == .Some(`hello`), "first line");
1635
+ * assert(iter.next() == .Some(`world`), "second line");
1636
+ * assert(iter.next() == .None, "done");
1637
+ * ```
1638
+ */
1639
+ lines : (fn(self : Self) -> StringLines)(
1640
+ StringLines(_string: self, _byte_index: usize(0))
1641
+ ),
1642
+
1643
+ /**
1644
+ * Returns a new string containing `n` copies of `self`.
1645
+ * Returns an empty string when `n == 0` or `self` is empty.
1646
+ *
1647
+ * ## Example
1648
+ * ```rust
1649
+ * assert(`ab`.repeat(usize(3)) == `ababab`, "repeat");
1650
+ * assert(`x`.repeat(usize(0)) == ``, "repeat 0");
1651
+ * ```
1652
+ */
1653
+ repeat : (fn(self: Self, n: usize) -> Self)({
1654
+ cond(
1655
+ (n == usize(0)) => Self(_bytes: .None),
1656
+ self.is_empty() => Self(_bytes: .None),
1657
+ true => {
1658
+ self_byte_len := self.bytes_len();
1659
+ total := (self_byte_len * n);
1660
+ buf := ArrayList(u8).with_capacity(total);
1661
+ i := usize(0);
1662
+ while ((i < n)),
1663
+ (i = (i + usize(1))),
1664
+ {
1665
+ match(self._bytes,
1666
+ .Some(al) => {
1667
+ j := usize(0);
1668
+ while ((j < self_byte_len)),
1669
+ (j = (j + usize(1))),
1670
+ {
1671
+ byte_opt := al.get(j);
1672
+ match(byte_opt,
1673
+ .Some(b) => { buf.push(b); },
1674
+ .None => ()
1675
+ );
1676
+ };
1677
+ },
1678
+ .None => ()
1679
+ );
1680
+ };
1681
+ Self(_bytes: .Some(buf))
1682
+ }
1683
+ )
1684
+ }),
1685
+
1686
+ /**
1687
+ * Join an `ArrayList(String)` with this string as separator.
1688
+ * Returns an empty string when `items` is empty.
1689
+ *
1690
+ * ## Example
1691
+ * ```rust
1692
+ * items := ArrayList(String).new();
1693
+ * items.push(`a`);
1694
+ * items.push(`b`);
1695
+ * items.push(`c`);
1696
+ * result := `, `.join(items);
1697
+ * assert(result == `a, b, c`, "join");
1698
+ * ```
1699
+ */
1700
+ join : (fn(self: Self, items: ArrayList(Self)) -> Self)({
1701
+ count := items.len();
1702
+ cond(
1703
+ (count == usize(0)) => Self(_bytes: .None),
1704
+ true => {
1705
+ sep_byte_len := self.bytes_len();
1706
+ total_bytes := usize(0);
1707
+ k := usize(0);
1708
+ while ((k < count)),
1709
+ (k = (k + usize(1))),
1710
+ {
1711
+ item_opt := items.get(k);
1712
+ match(item_opt,
1713
+ .Some(item) => {
1714
+ total_bytes = (total_bytes + item.bytes_len());
1715
+ },
1716
+ .None => ()
1717
+ );
1718
+ };
1719
+ cond(
1720
+ (count > usize(1)) => {
1721
+ total_bytes = (total_bytes + (sep_byte_len * (count - usize(1))));
1722
+ },
1723
+ true => ()
1724
+ );
1725
+ buf := ArrayList(u8).with_capacity(total_bytes);
1726
+ m := usize(0);
1727
+ while ((m < count)),
1728
+ (m = (m + usize(1))),
1729
+ {
1730
+ item_opt := items.get(m);
1731
+ match(item_opt,
1732
+ .Some(item) => {
1733
+ match(item._bytes,
1734
+ .Some(al) => {
1735
+ p := usize(0);
1736
+ while ((p < al.len())),
1737
+ (p = (p + usize(1))),
1738
+ {
1739
+ byte_opt := al.get(p);
1740
+ match(byte_opt,
1741
+ .Some(b) => { buf.push(b); },
1742
+ .None => ()
1743
+ );
1744
+ };
1745
+ },
1746
+ .None => ()
1747
+ );
1748
+ cond(
1749
+ (m < (count - usize(1))) => {
1750
+ match(self._bytes,
1751
+ .Some(sep_al) => {
1752
+ q := usize(0);
1753
+ while ((q < sep_byte_len)),
1754
+ (q = (q + usize(1))),
1755
+ {
1756
+ byte_opt := sep_al.get(q);
1757
+ match(byte_opt,
1758
+ .Some(b) => { buf.push(b); },
1759
+ .None => ()
1760
+ );
1761
+ };
1762
+ },
1763
+ .None => ()
1764
+ );
1765
+ },
1766
+ true => ()
1767
+ );
1768
+ },
1769
+ .None => ()
1770
+ );
1771
+ };
1772
+ cond(
1773
+ (buf.len() == usize(0)) => Self(_bytes: .None),
1774
+ true => Self(_bytes: .Some(buf))
1775
+ )
1776
+ }
1777
+ )
1778
+ })
1779
+ );
1780
+
1561
1781
  /// === Numeric parsing methods ===
1562
1782
 
1563
1783
  impl(String,
@@ -1932,9 +2152,33 @@ impl(String, Index(usize)(
1932
2152
  )
1933
2153
  ));
1934
2154
 
2155
+ /// Clone implementation — produces an independent byte-level copy.
2156
+ impl(String, Clone(
2157
+ clone : (fn(self: *(Self)) -> Self)(
2158
+ match(self._bytes,
2159
+ .None => Self(_bytes: .None),
2160
+ .Some(al) => {
2161
+ n := al.len();
2162
+ cond(
2163
+ (n == usize(0)) => Self(_bytes: .None),
2164
+ true => {
2165
+ buf := ArrayList(u8).with_capacity(n);
2166
+ match(al._ptr,
2167
+ .Some(ptr) => buf.extend_from_ptr(ptr, n),
2168
+ .None => ()
2169
+ );
2170
+ Self(_bytes: .Some(buf))
2171
+ }
2172
+ )
2173
+ }
2174
+ )
2175
+ )
2176
+ ));
2177
+
1935
2178
  export
1936
2179
  String,
1937
2180
  StringError,
1938
2181
  StringChars,
1939
- StringBytes
2182
+ StringBytes,
2183
+ StringLines
1940
2184
  ;