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
//! 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,
/// the wall clock, and diagnostic messages (`StdHost` prints them to
/// stderr).
///
/// `MMix::new()` installs `StdHost`, which writes to the process's own
/// stdout and stderr. `MMix::with_host` accepts any `Host`, which is how an
/// embedder (a wasm host with no stdout, a test harness that wants to
/// inspect bytes rather than print them) captures what the machine emits
/// instead of losing it to 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
/// route through `Host`; a fd 0 (`StdIn`) read always fails, since `Host`
/// has no read primitive.
///
/// `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
/// `stdout`/`stderr` 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 (fd 3+
/// reads its `File` from `file_handles`), but `StdHost` is a general `Host`
/// implementation, so it treats any other fd as an error rather than
/// assuming that invariant.
;