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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
//! The machine description language (`ROADMAP.md` §5).
//!
//! A `.machine` file is how a person describes a machine to rsemu: oscillators,
//! address spaces, devices, the memory map and the wires between them. It is
//! the framework's user interface, and §5 is blunt about why that matters —
//! most people meet rsemu through a syntax error.
//!
//! # What is here
//!
//! The whole front end, and the layer that turns its output into a machine:
//!
//! ```text
//! lex → parse (spans preserved) → resolve → validate → realize → run
//! ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//! all of it, ending in [`build`]
//! ```
//!
//! | Module | Role |
//! | --- | --- |
//! | [`builtin`] | the classes the language ships with: `ram` |
//! | [`catalog`] | what this build can emulate: classes, bindings, machines |
//! | [`span`] | byte-offset spans, and mapping them to `file:line:col` |
//! | [`diag`] | one precise error, rendered with the line and a caret |
//! | [`lexer`] | hand-written tokenizer, no generator and no regex |
//! | [`ast`] | the syntax tree, with a span on every node |
//! | [`parser`] | recursive descent, depth-guarded |
//! | [`rational`] | exact frequencies, because 236250000/11 Hz is not an integer |
//! | [`sources`] | several files in one span space, and the `include` seam |
//! | [`resolver`] | params, includes, templates, loops, links, cycles |
//! | [`mod@validate`] | classes, properties, pins, address ranges, wire cycles |
//! | [`mod@realize`] | construct, map, wire, bind, reset, sweep |
//! | [`mod@machine`] | the assembled [`Machine`], its snapshot and its run loop |
//!
//! # What is not here, and where it plugs in
//!
//! * **The device registry as a validator input.** [`validate()`] takes a
//! [`ClassTable`] rather than reaching for `core::registry`, and [`build`]
//! passes whatever the caller put in
//! [`BuildOptions::classes`]. It cannot derive one: `DeviceClass` declares a
//! class's *properties* but not its pins or its mappable regions, so a table
//! built from the registry would reject every `map x = dev.regs` as naming a
//! region the class does not have.
//! * **The JSON projection** (`rsemu convert`, §2), which will read the same
//! AST: one AST, two syntaxes.
//! * **File loading.** This module is `no_std` and never touches a
//! filesystem. An `include` goes through
//! [`sources::IncludeLoader`], so the caller owns the search
//! path and the sandbox.
//!
//! # Example
//!
//! ```
//! use rsemu::machine::resolver::ResolveOptions;
//! use rsemu::machine::resolve_file;
//!
//! let text = r#"
//! machine "nes" {
//! param region = "ntsc"
//! osc master = 236250000/11 Hz # not an integer number of hertz
//! space cpubus { width = 16, unassigned = open-bus }
//! object cpu "mos6502" { clock = master / 12, space = cpubus }
//! object ppu "nes.ppu" { clock = master / 4 }
//! wire ppu.nmi -> cpu.nmi
//! }
//! "#;
//! let machine = resolve_file("nes.machine", text, &ResolveOptions::new())?;
//!
//! // The crystal stays rational; the ratios are exact integers (§4.2).
//! assert_eq!(machine.oscillators[0].hz.denominator(), 11);
//! let cpu = machine.objects[0].clock.expect("a clock");
//! let ppu = machine.objects[1].clock.expect("a clock");
//! assert_eq!((cpu.div, ppu.div), (12, 4));
//! assert_eq!(machine.wires[0].to.port, "nmi");
//! # Ok::<(), rsemu::Error>(())
//! ```
// `machine::machine` reads oddly, but the type it holds is `Machine` and the
// module is where a reader looks for it.
pub use crateSourceUnit;
pub use crateDiagnostic;
pub use crateMachine;
pub use crateparse;
pub use crateRational;
pub use crate;
pub use crate;
pub use crate;
pub use crate;
pub use crate;
/// Parse a machine description, reporting failures as [`Error::Config`].
///
/// The convenience entry point for everything above the front end: the error
/// carries `file:line:col` in `at` and the message plus a caret in `message`,
/// so a CLI can print it with `eprintln!("{err}")` and be done
/// (`ROADMAP.md` §5: errors carry file:line:col and a caret, always).
///
/// Use [`parse`] directly when the caller wants the [`Diagnostic`] itself — to
/// render it differently, or to attach it to a larger report.
///
/// [`Error::Config`]: crate::core::Error::Config
/// Parse and resolve a self-contained machine description.
///
/// The whole pipeline short of validation, for a description that needs no
/// `include` and no search path — the common case for a test, a fixture or an
/// embedded string. A description that includes other files, or that should be
/// checked against a device registry, wants [`SourceMap`] plus [`resolve`] and
/// [`validate()`] directly, so that diagnostics can name whichever file they
/// point into.
///
/// [`Error::Config`]: crate::core::Error::Config
/// Everything the pipeline needs beyond the source text and the registry.
///
/// A struct rather than eight arguments, and owned rather than borrowed, so a
/// caller can build one once and reuse it for every machine it loads.
/// The whole pipeline: source text to a machine that can run.
///
/// ```text
/// lex → parse → resolve → validate → realize
/// ```
///
/// Front-end failures are rendered against the source, so the error carries
/// `file:line:col` and a caret (§5). Realize-time failures name the instance
/// instead — see [`mod@realize`] for why that is a seam rather than a choice.
///
/// ```
/// use rsemu::core::Registry;
/// use rsemu::machine::{BuildOptions, build};
///
/// // No device features are enabled in this build, so the machine this
/// // registry can assemble is one with no devices in it — which still has
/// // spaces, a scheduler and a snapshot.
/// let machine = build(
/// "empty.machine",
/// r#"machine "empty" { space cpubus { width = 16, unassigned = read-as-ones } }"#,
/// &Registry::new(),
/// &BuildOptions::new(),
/// )?;
/// assert_eq!(machine.name(), "empty");
/// assert_eq!(machine.spaces().len(), 1);
/// assert!(machine.devices().is_empty());
/// # Ok::<(), rsemu::Error>(())
/// ```
///
/// # Errors
///
/// A syntax error, an unresolved name, a failed validation, or anything
/// [`realize()`] refuses.