1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
//! The `Host` trait, its `Box` impl, and `StdHost`.
use TrapCode;
use Any;
use ;
use SystemTime;
/// Routes every process-level effect an `MMix` produces: writes to fd 1/2, a
/// read from fd 0, the wall clock, and diagnostic messages (`StdHost` prints
/// them to stderr).
///
/// `MMix::new()` installs `StdHost`, which reads the process's own stdin
/// and writes to its stdout and stderr. `MMix::with_host` accepts any
/// `Host`, which is how an embedder (a wasm host with no stdin or stdout, a
/// test harness that wants to feed and inspect bytes rather than a real
/// stream) replaces what the machine reads and emits instead of routing it
/// through the process.
///
/// File-descriptor traps (`Fopen`/`Fclose`/`Fread`/`Fgets`/`Fgetws`/
/// `Fwrite`/`Fseek`/`Ftell` on a handle above 2, and fd 3+ of `Fputs`/
/// `Fputc`/`Fputws`) do not go through the host — they keep using `std::fs`
/// directly and fail naturally on platforms without a filesystem. Handles
/// 0-2 belong to the host: `Fopen`/`Fclose` reject them. Fd 1 and 2 writes,
/// and a fd 0 (`StdIn`) read, route through `Host`.
///
/// `flush` and `trap` have no-op defaults, so an embedder implements only
/// what it needs. The trait is object-safe — `MMix` stores it as
/// `Box<dyn Host>` — and any method added in a future release will carry a
/// default, so implementors do not break.
///
/// `MMix::host_mut` and `MMix::into_host` reach a host moved in with
/// `with_host` again, by reference or by consuming the machine. A host that
/// wants its caller to read what it captured without borrowing the `MMix`
/// can instead hold its buffers behind a shared handle —
/// `Rc<RefCell<Vec<u8>>>` or similar — cloned *before* the host is moved
/// in, so the caller keeps one clone and the `MMix` owns the other.
///
/// Because `MMix` stores the host as `Box<dyn Host>`, it is none of `Send`,
/// `Sync`, `UnwindSafe`, or `RefUnwindSafe`. The intended embedders are
/// single-threaded — a browser playground, a test harness — and capture into
/// `Rc<RefCell<_>>`; see the [`MMix`] docs.
///
/// ```
/// use checksmix::{Host, MMix, TrapCode};
/// use std::cell::RefCell;
/// use std::rc::Rc;
///
/// struct Capture(Rc<RefCell<Vec<u8>>>);
///
/// impl Host for Capture {
/// fn write(&mut self, _fd: u8, bytes: &[u8]) -> std::io::Result<()> {
/// self.0.borrow_mut().extend_from_slice(bytes);
/// Ok(())
/// }
/// fn now_micros(&mut self) -> u64 { 0 }
/// fn diagnostic(&mut self, _msg: &str) {}
/// fn trap(&mut self, code: TrapCode, _arg: u8, _before: u64, _after: u64) {
/// // TrapCode is #[non_exhaustive]: a wildcard arm is required
/// match code {
/// TrapCode::Fputc => {}
/// _ => {}
/// }
/// }
/// }
///
/// // Keep one clone of the buffer; the machine owns the other.
/// let out = Rc::new(RefCell::new(Vec::new()));
/// let mut mmix = MMix::with_host(Capture(out.clone()));
///
/// mmix.set_register(255, u64::from(b'X'));
/// mmix.write_tetra(0, 0x00008001); // TRAP 0, Fputc (#80), fd 1
/// mmix.execute_instruction();
///
/// assert_eq!(&*out.borrow(), b"X");
/// ```
/// Lets a caller that already holds a boxed host — one selected at runtime
/// from several types, say — pass it to `MMix::with_host` directly.
///
/// There is deliberately no matching impl for `&mut H`. Both `Box` and `&mut`
/// are `#[fundamental]`, so downstream may implement `Host` for either and a
/// blanket impl added later could collide — but `with_host` takes `H: 'static`
/// by value, so a borrowed host is only usable when the borrow is `'static`,
/// which is rare enough not to buy the surface.
/// The `Host` behind `MMix::new()`: the process's own I/O — locked
/// `stdin`/`stdout`/`stderr` reads and writes, `stdout().flush()` on halt,
/// `SystemTime` for the clock, and `eprintln!` for diagnostics.
///
/// `write_bytes_to_fd` only ever calls `Host::write` with fd 1 or 2, and
/// `Host::read` is only ever called with fd 0 (fd 3+ uses its `File` from
/// `file_handles` directly), but `StdHost` is a general `Host`
/// implementation, so it treats any other fd as an error rather than
/// assuming those invariants.
;