1use std::error::Error;
3use std::fmt::{self, Display, Formatter};
4use std::str::FromStr;
5use std::time::Duration;
6
7use serde::Serialize;
8
9use base::instruction::Instruction;
10use base::prelude::*;
11
12use super::bugreport::{IssueType, bug_report_url};
13use super::diagnostics::{CurrentInstructionDiagnostics, DiagnosticFetcher};
14
15#[derive(Debug, Clone)]
17pub enum BadMemOp {
18 Read(Unsigned36Bit),
20 Write(Unsigned36Bit),
22}
23
24impl Display for BadMemOp {
25 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
26 match self {
27 BadMemOp::Read(addr) => write!(f, "memory read from {addr:>013o} failed"),
28 BadMemOp::Write(addr) => write!(f, "memory write to {addr:>013o} failed"),
29 }
30 }
31}
32
33#[derive(Debug, PartialEq, Eq)]
35pub enum AlarmMaskability {
36 Maskable,
37 Unmaskable,
38}
39
40#[allow(clippy::upper_case_acronyms)]
50#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, PartialOrd, Ord, Serialize)]
51pub enum AlarmKind {
52 PSAL,
53 OCSAL,
54 QSAL,
55 IOSAL,
56 MISAL,
57 ROUNDTUITAL,
58 DEFERLOOPAL,
59 BUGAL,
60}
61
62impl Display for AlarmKind {
63 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
64 f.write_str(match self {
65 AlarmKind::PSAL => "PSAL",
66 AlarmKind::OCSAL => "OCSAL",
67 AlarmKind::QSAL => "QSAL",
68 AlarmKind::IOSAL => "IOSAL",
69 AlarmKind::MISAL => "MISAL",
70 AlarmKind::ROUNDTUITAL => "ROUNDTUITAL",
71 AlarmKind::DEFERLOOPAL => "DEFERLOOPAL",
72 AlarmKind::BUGAL => "BUGAL",
73 })
74 }
75}
76
77impl AlarmKind {
78 #[must_use]
80 pub fn maskable(&self) -> AlarmMaskability {
81 match self {
82 AlarmKind::BUGAL | AlarmKind::DEFERLOOPAL | AlarmKind::ROUNDTUITAL => {
83 AlarmMaskability::Unmaskable
84 }
85 _ => AlarmMaskability::Maskable,
86 }
87 }
88
89 #[must_use]
90 pub const fn all_alarm_kinds() -> [AlarmKind; 8] {
91 [
92 AlarmKind::PSAL,
93 AlarmKind::OCSAL,
94 AlarmKind::QSAL,
95 AlarmKind::IOSAL,
96 AlarmKind::MISAL,
97 AlarmKind::ROUNDTUITAL,
98 AlarmKind::DEFERLOOPAL,
99 AlarmKind::BUGAL,
100 ]
101 }
102}
103
104#[derive(Debug)]
105pub struct UnknownAlarmName(String);
106
107impl Display for UnknownAlarmName {
108 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
109 write!(f, "unknown alarm name '{}'", self.0)
110 }
111}
112
113impl Error for UnknownAlarmName {}
114
115impl FromStr for AlarmKind {
116 type Err = UnknownAlarmName;
117
118 fn from_str(s: &str) -> Result<Self, Self::Err> {
119 match s {
120 "PSAL" => Ok(AlarmKind::PSAL),
121 "OCSAL" => Ok(AlarmKind::OCSAL),
122 "QSAL" => Ok(AlarmKind::QSAL),
123 "IOSAL" => Ok(AlarmKind::IOSAL),
124 "MISAL" => Ok(AlarmKind::MISAL),
125 "ROUNDTUITAL" => Ok(AlarmKind::ROUNDTUITAL),
126 "DEFERLOOPAL" => Ok(AlarmKind::DEFERLOOPAL),
127 "BUGAL" => Ok(AlarmKind::BUGAL),
128 _ => Err(UnknownAlarmName(s.to_owned())),
129 }
130 }
131}
132
133#[test]
134fn test_alarm_kind_round_trip() {
135 for orig_kind in AlarmKind::all_alarm_kinds() {
136 let name = orig_kind.to_string();
137 match AlarmKind::from_str(name.as_str()) {
138 Ok(k) => {
139 assert_eq!(k, orig_kind);
140 }
141 Err(_) => {
142 panic!("unable to round-trip alarm kind {orig_kind:?}");
143 }
144 }
145 }
146 assert!(AlarmKind::from_str("this is not an alarm name").is_err());
147}
148
149#[derive(Debug, Clone)]
151pub enum BugActivity {
152 Io,
155 Opcode,
158 AlarmHandling,
161}
162
163#[allow(clippy::upper_case_acronyms)]
189#[derive(Debug, Clone)]
190pub enum AlarmDetails {
191 PSAL(u32, String),
195
196 OCSAL(Instruction, String),
208
209 QSAL(Instruction, BadMemOp, String),
212
213 IOSAL {
216 unit: Unsigned6Bit,
218 operand: Option<Unsigned18Bit>,
219 message: String,
220 },
221
222 MISAL { affected_unit: Unsigned6Bit },
227
228 ROUNDTUITAL {
231 explanation: String,
232 bug_report_url: &'static str,
233 },
234
235 DEFERLOOPAL {
238 address: Unsigned18Bit,
240 },
241
242 BUGAL {
244 activity: BugActivity,
246 diagnostics: CurrentInstructionDiagnostics,
248 message: String,
250 },
251}
252
253#[allow(clippy::upper_case_acronyms)]
255#[derive(Debug, Clone)]
256pub struct Alarm {
257 pub sequence: Option<SequenceNumber>,
258 pub details: AlarmDetails,
259}
260
261impl Alarm {
262 #[must_use]
263 pub fn kind(&self) -> AlarmKind {
264 self.details.kind()
265 }
266}
267
268impl AlarmDetails {
269 #[must_use]
270 pub fn kind(&self) -> AlarmKind {
271 match self {
272 AlarmDetails::PSAL(_, _) => AlarmKind::PSAL,
273 AlarmDetails::OCSAL(_, _) => AlarmKind::OCSAL,
274 AlarmDetails::QSAL(_, _, _) => AlarmKind::QSAL,
275 AlarmDetails::IOSAL { .. } => AlarmKind::IOSAL,
276 AlarmDetails::MISAL { .. } => AlarmKind::MISAL,
277 AlarmDetails::ROUNDTUITAL { .. } => AlarmKind::ROUNDTUITAL,
278 AlarmDetails::DEFERLOOPAL { .. } => AlarmKind::DEFERLOOPAL,
279 AlarmDetails::BUGAL { .. } => AlarmKind::BUGAL,
280 }
281 }
282}
283
284impl Display for Alarm {
285 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
286 if let Some(seq) = self.sequence {
287 write!(f, "in sequence {seq:o}, {}", self.details)
288 } else {
289 write!(f, "{}", self.details)
290 }
291 }
292}
293
294impl Display for AlarmDetails {
295 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
296 use AlarmDetails::*;
297 match self {
298 QSAL(instruction, op, msg) => {
299 write!(
300 f,
301 "QSAL: during execution of instruction {instruction:?}, {op}: {msg}",
302 )
303 }
304 PSAL(address, msg) => {
305 write!(
306 f,
307 "PSAL: P register set to illegal address {address:>06o}: {msg}",
308 )
309 }
310 OCSAL(inst, msg) => {
311 write!(
312 f,
313 "OCSAL: N register set to invalid instruction {:>012o}: {}",
314 inst.bits(),
315 msg
316 )
317 }
318 ROUNDTUITAL {
319 explanation,
320 bug_report_url,
321 } => {
322 write!(
323 f,
324 "ROUNDTUITAL: the program used a feature not supported in the emulator: {explanation}. See feature request at {bug_report_url}",
325 )
326 }
327
328 IOSAL {
329 unit,
330 operand,
331 message,
332 } => {
333 write!(f, "IOSAL: I/O alarm during operation on unit {unit:o}")?;
334 if let Some(oper) = operand {
335 write!(f, " with operand {oper}")?;
336 }
337 write!(f, ": {message}")
338 }
339
340 MISAL { affected_unit } => write!(
341 f,
342 "MISAL: program too slow; missed data for unit {affected_unit:o}"
343 ),
344
345 BUGAL {
346 diagnostics,
347 message,
348 activity,
349 } => {
350 let (issue_type, activity_desc): (Option<IssueType>, &'static str) = match activity
351 {
352 BugActivity::AlarmHandling => (None, "alarm handling"),
353 BugActivity::Io => (Some(IssueType::Io), "I/O"),
354 BugActivity::Opcode => (Some(IssueType::Opcode), "instruction execution"),
355 };
356 let report_url = bug_report_url(message, issue_type);
357 write!(
358 f,
359 "BUGAL: encountered a bug in enumator {activity_desc} during execution of {diagnostics}: {message}; please report this as a bug at {report_url}",
360 )
361 }
362 DEFERLOOPAL { address } => {
363 write!(
364 f,
365 "DEFERLOOPAL: infinite loop in deferred address at {address:>012o}",
366 )
367 }
368 }
369 }
370}
371
372impl Error for Alarm {}
373
374#[derive(Debug)]
377pub struct UnmaskedAlarm {
378 pub alarm: Alarm,
379 pub address: Option<Address>,
380 pub when: Duration,
381}
382
383impl Display for UnmaskedAlarm {
384 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
385 write!(f, "unmasked alarm {}", self.alarm)?;
386 if let Some(address) = self.address {
387 write!(f, "at address {address}")
388 } else {
389 Ok(())
390 }
391 }
392}
393
394pub trait Alarmer {
396 fn fire_if_not_masked<F: DiagnosticFetcher>(
398 &mut self,
399 alarm_instance: Alarm,
400 get_diags: F,
401 ) -> Result<(), Alarm>;
402 fn always_fire<F: DiagnosticFetcher>(&mut self, alarm_instance: Alarm, get_diags: F) -> Alarm;
404}