1mod clock;
3mod lw;
4mod sleep;
5
6use std::ffi::{OsStr, OsString};
7use std::fmt::{self, Debug, Display, Formatter};
8use std::fs::OpenOptions;
9use std::io::{BufReader, Read};
10use std::str::FromStr;
11use std::time::Duration;
12
13use clap::{ArgAction::Set, Parser, ValueEnum};
14use tracing::{Level, event};
15use tracing_subscriber::filter::{EnvFilter, LevelFilter};
16use tracing_subscriber::prelude::*;
17
18use base::prelude::*;
19use clock::{BasicClock, Clock};
20use cpu::{
21 self, Alarm, AlarmDetails, MemoryConfiguration, OutputEvent, ResetMode, RunMode, Tx2,
22 UnmaskedAlarm,
23};
24
25const AUTHOR: &str = "James Youngman <james@youngman.org>";
29
30fn run(
31 tx2: &mut Tx2,
32 clk: &mut BasicClock,
33 sleep_multiplier: Option<f64>,
34) -> Result<(), Box<dyn std::error::Error>> {
35 if let Err(e) = tx2.codabo(&clk.make_fresh_context(), &ResetMode::ResetTSP) {
36 event!(Level::ERROR, "CODABO failed: {}", e);
37 return Err(Box::new(e));
38 }
39
40 {
45 let now = clk.now();
46 tx2.set_next_execution_due(now, Some(now));
47 }
48 tx2.set_run_mode(RunMode::Running);
49
50 match run_until_alarm(tx2, clk, sleep_multiplier) {
51 UnmaskedAlarm {
52 alarm,
53 address: Some(addr),
54 when: _,
55 } => {
56 event!(
57 Level::ERROR,
58 "Execution stopped at address {:o}: {}",
59 addr,
60 alarm
61 );
62 }
63 UnmaskedAlarm {
64 alarm,
65 address: None,
66 when: _,
67 } => {
68 event!(Level::ERROR, "Execution stopped: {}", alarm);
69 }
70 };
71 if let Err(e) = tx2.disconnect_all_devices(&clk.make_fresh_context()) {
72 event!(Level::ERROR, "Failed in device shutdown: {}", e);
73 return Err(Box::new(e));
74 }
75 Ok(())
76}
77
78fn run_until_alarm(
79 tx2: &mut Tx2,
80 clk: &mut BasicClock,
81 sleep_multiplier: Option<f64>,
82) -> UnmaskedAlarm {
83 let mut sleeper = sleep::MinimalSleeper::new(Duration::from_millis(2));
84 let mut lw66 = lw::LincolnStreamWriter::new();
85
86 let result: UnmaskedAlarm = loop {
87 {
88 let now = clk.now();
89 let next = tx2.next_tick();
90 if now < next {
91 let interval = next - now;
92 sleep::time_passes(clk, &mut sleeper, &interval, sleep_multiplier);
93 }
94 clk.advance_to_simulated_time(next);
95 }
96 let tick_context = clk.make_fresh_context();
97 match tx2.tick(&tick_context) {
98 Ok(maybe_output) => {
99 match maybe_output {
100 None => (),
101 Some(OutputEvent::LincolnWriterPrint { unit, ch }) => {
102 if unit == u6!(0o66) {
103 if let Err(e) = lw66.write(ch) {
104 event!(Level::ERROR, "output error: {}", e);
105 break UnmaskedAlarm {
109 alarm: Alarm {
110 sequence: None,
111 details: AlarmDetails::MISAL {
112 affected_unit: u6!(0o66),
113 },
114 },
115 address: None,
116 when: tick_context.simulated_time,
117 };
118 }
119 } else {
120 event!(
121 Level::WARN,
122 "discarding Lincoln Writer output for unit {:o}",
123 unit,
124 );
125 }
126 }
127 }
128 }
129 Err(unmasked_alarm) => {
130 break unmasked_alarm;
131 }
132 }
133 let next_tick = tx2.next_tick();
134 if next_tick <= tick_context.simulated_time {
135 event!(
136 Level::WARN,
137 "Tx2::tick is not advancing the system clock (next tick {:?} <= current tick {:?})",
138 next_tick,
139 tick_context.simulated_time,
140 );
141 }
142 };
143 lw66.disconnect();
144 result
145}
146
147#[derive(Debug, Clone, PartialEq, Eq, ValueEnum)]
149enum PanicOnUnmaskedAlarm {
150 No,
154 Yes,
157}
158
159impl FromStr for PanicOnUnmaskedAlarm {
160 type Err = String;
161
162 fn from_str(s: &str) -> Result<Self, String> {
163 match s {
164 "true" | "yes" => Ok(PanicOnUnmaskedAlarm::Yes),
165 "false" | "no" => Ok(PanicOnUnmaskedAlarm::No),
166 _ => Err(format!(
167 "unexpected value '{s}': expected 'true', 'false', 'yes' or 'no'",
168 )),
169 }
170 }
171}
172
173#[derive(Parser, Debug)]
175#[command(author = AUTHOR, version, about, long_about = None)]
176struct Cli {
177 #[arg(action = Set, long = "speed-multiplier")]
179 speed_multiplier: Option<String>,
180
181 #[arg(action = Set, long = "panic-on-unmasked-alarm", value_enum)]
185 panic_on_unmasked_alarm: Option<PanicOnUnmaskedAlarm>,
186
187 #[arg(action = Set)]
189 tape: Option<OsString>,
190}
191
192#[derive(Debug)]
193struct BadSpeedMultiplier(f64);
194
195impl Display for BadSpeedMultiplier {
196 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
197 write!(
198 f,
199 "speed multiplier {} is too small for reliable arithmetic operations",
200 self.0
201 )
202 }
203}
204
205impl std::error::Error for BadSpeedMultiplier {}
206
207fn run_simulator() -> Result<(), Box<dyn std::error::Error>> {
208 let cli = Cli::parse();
209
210 let env_filter = EnvFilter::builder()
212 .with_default_directive(LevelFilter::INFO.into())
213 .from_env_lossy();
214
215 tracing_subscriber::registry()
217 .with(tracing_subscriber::fmt::layer())
218 .with(env_filter)
219 .init();
220
221 let mem_config = MemoryConfiguration {
222 with_u_memory: false,
223 };
224
225 fn file_size_guess(file_name: &OsStr) -> Option<usize> {
226 match std::fs::metadata(file_name) {
227 Ok(metadata) => usize::try_from(metadata.len()).ok(),
228 Err(_) => None,
229 }
230 }
231
232 let tape_data: Option<Vec<u8>> = match cli.tape.as_ref() {
233 None => {
234 event!(
235 Level::WARN,
236 "No paper tapes were specified on the command line, so no program will be loaded"
237 );
238 None
239 }
240 Some(file_name) => {
241 match OpenOptions::new()
242 .read(true)
243 .open(file_name)
244 .map(BufReader::new)
245 {
246 Ok(mut r) => {
247 let mut buf: Vec<u8> =
248 Vec::with_capacity(file_size_guess(file_name).unwrap_or(0));
249 if let Err(e) = r.read_to_end(&mut buf) {
250 return Err(Box::new(e));
251 } else {
252 Some(buf)
253 }
254 }
255 Err(e) => {
256 return Err(Box::new(e));
257 }
258 }
259 }
260 };
261
262 let speed_multiplier: Option<f64> = match cli.speed_multiplier.as_ref() {
263 None => {
264 event!(
265 Level::WARN,
266 "No --speed-multiplier option specified, running at maximum speed"
267 );
268 None
269 }
270 Some(value) => match value.as_str() {
271 "MAX" => {
272 event!(
273 Level::INFO,
274 "--speed-multiplier=MAX, running at maximum speed"
275 );
276 None
277 }
278 some_number => match some_number.parse::<f64>() {
279 Ok(x) => {
280 event!(
281 Level::INFO,
282 "--speed-multiplier={}, running at speed multiplier {}",
283 some_number,
284 x
285 );
286 Some(x)
287 }
288 Err(e) => {
289 return Err(Box::new(e));
290 }
291 },
292 },
293 };
294 let sleep_multiplier = match speed_multiplier {
295 None => None,
296 Some(sp) => {
297 let sleep_multiplier = sp.recip();
298 if sleep_multiplier.is_finite() {
299 Some(sleep_multiplier)
300 } else {
301 return Err(Box::new(BadSpeedMultiplier(sp)));
302 }
303 }
304 };
305
306 let mut clk: BasicClock = BasicClock::new();
307 let panic_on_unmasked_alarm = match cli.panic_on_unmasked_alarm {
310 Some(PanicOnUnmaskedAlarm::Yes) => cpu::PanicOnUnmaskedAlarm::Yes,
311 Some(PanicOnUnmaskedAlarm::No) | None => cpu::PanicOnUnmaskedAlarm::No,
312 };
313 let initial_context = clk.make_fresh_context();
314 let mut tx2 = Tx2::new(&initial_context, panic_on_unmasked_alarm, &mem_config);
315 if let Some(tape) = tape_data
316 && let Err(e) = tx2.mount_paper_tape(&initial_context, tape)
317 {
318 return Err(Box::new(e));
319 }
320 run(&mut tx2, &mut clk, sleep_multiplier)
321}
322
323fn main() {
324 match run_simulator() {
325 Err(e) => {
326 eprintln!("{e}");
327 std::process::exit(1);
328 }
329 Ok(()) => {
330 std::process::exit(0);
331 }
332 }
333}