1use crate::cics::Condition;
6use crate::vocab::Pos;
7use std::fmt;
8use zarch::check::ProgramCheck;
9
10#[derive(Clone, Debug, PartialEq, Eq)]
11pub struct Abend {
12 pub code: AbendCode,
13 pub message: String,
14 pub pos: Pos,
15 pub file: Option<String>,
19}
20
21impl Abend {
22 pub fn check(c: ProgramCheck, pos: Pos) -> Self {
24 let (id, what) = match c {
25 ProgramCheck::Specification => ("CEE3206S", "a specification"),
26 ProgramCheck::Data => ("CEE3207S", "a data"),
27 ProgramCheck::FixedPointOverflow => ("CEE3208S", "a fixed-point overflow"),
28 ProgramCheck::FixedPointDivide => ("CEE3209S", "a fixed-point divide"),
29 ProgramCheck::DecimalOverflow => ("CEE3210S", "a decimal-overflow"),
30 ProgramCheck::DecimalDivide => ("CEE3211S", "a decimal-divide"),
31 ProgramCheck::HfpExponentOverflow => ("CEE3212S", "an exponent-overflow"),
32 ProgramCheck::HfpExponentUnderflow => ("CEE3213S", "an exponent-underflow"),
33 ProgramCheck::HfpSignificance => ("CEE3214S", "a significance"),
34 ProgramCheck::HfpDivide => ("CEE3215S", "a floating-point divide"),
35 };
36 let code = AbendCode::Check(c);
37 let message = format!("{id} The system detected {what} exception (System Completion Code={}).", &code.as_str()[1..]);
38 Self { code, message, pos, file: None }
39 }
40
41 pub fn ironwork(message: impl Into<String>, pos: Pos) -> Self {
42 Self { code: AbendCode::Ironwork, message: message.into(), pos, file: None }
43 }
44
45 pub fn zero_power(pos: Pos) -> Self {
48 Self { code: AbendCode::Le(LeCondition::ZeroPower), message: "IGZ0050S A zero base was raised to a negative power in an exponentiation expression.".into(), pos, file: None }
49 }
50
51 pub fn size_error(&self) -> bool {
53 self.code.size_error()
54 }
55}
56
57#[derive(Clone, Copy, Debug, PartialEq, Eq)]
62pub enum LeCondition {
63 ZeroPower,
65 FileStatus,
67}
68
69#[derive(Clone, Copy, Debug, PartialEq, Eq)]
70pub enum Ending {
71 Goback,
72 StopRun,
73 EndOfProgram,
74}
75
76#[derive(Clone, Debug, PartialEq, Eq)]
77pub enum AbendCode {
78 Check(ProgramCheck),
80 Protection,
82 ModuleNotFound,
84 TimeLimit,
86 Io(FileStatus),
88 Cics(String),
90 User(String),
92 Le(LeCondition),
94 Requested { code: String, clean_up: bool },
96 Ironwork,
97 Exec,
98 Sql,
99 SqlReplay,
101 Java,
102 Signal(Signal),
103}
104
105#[derive(Clone, Copy, Debug, PartialEq, Eq)]
107pub enum Signal {
108 StopRun,
111 GoBack,
113 SortStopped,
115 ClosedOutput,
117 DeclarativeExit,
120}
121
122impl Signal {
123 const ALL: [Self; 5] = [Self::StopRun, Self::GoBack, Self::SortStopped, Self::ClosedOutput, Self::DeclarativeExit];
124
125 fn text(self) -> &'static str {
126 match self {
127 Self::StopRun => "REPORT-STOP-RUN",
128 Self::GoBack => "REPORT-GOBACK",
129 Self::SortStopped => "SORT-STOPPED",
130 Self::ClosedOutput => "CLOSED-OUTPUT",
131 Self::DeclarativeExit => "DECLARATIVE-EXIT",
132 }
133 }
134}
135
136const CHECKS: [ProgramCheck; 10] = [
137 ProgramCheck::Specification,
138 ProgramCheck::Data,
139 ProgramCheck::FixedPointOverflow,
140 ProgramCheck::FixedPointDivide,
141 ProgramCheck::DecimalOverflow,
142 ProgramCheck::DecimalDivide,
143 ProgramCheck::HfpExponentOverflow,
144 ProgramCheck::HfpExponentUnderflow,
145 ProgramCheck::HfpSignificance,
146 ProgramCheck::HfpDivide,
147];
148
149fn check_code(c: ProgramCheck) -> &'static str {
150 match c {
151 ProgramCheck::Specification => "S0C6",
152 ProgramCheck::Data => "S0C7",
153 ProgramCheck::FixedPointOverflow => "S0C8",
154 ProgramCheck::FixedPointDivide => "S0C9",
155 ProgramCheck::DecimalOverflow => "S0CA",
156 ProgramCheck::DecimalDivide => "S0CB",
157 ProgramCheck::HfpExponentOverflow => "S0CC",
158 ProgramCheck::HfpExponentUnderflow => "S0CD",
159 ProgramCheck::HfpSignificance => "S0CE",
160 ProgramCheck::HfpDivide => "S0CF",
161 }
162}
163
164impl AbendCode {
165 pub fn user(code: u16) -> Self {
166 Self::User(format!("U{code:04}"))
167 }
168
169 pub fn as_str(&self) -> &str {
170 match self {
171 Self::Check(c) => check_code(*c),
172 Self::Protection => "S0C4",
173 Self::ModuleNotFound => "S806",
174 Self::TimeLimit => "S322",
175 Self::Io(status) => status.abend_code(),
176 Self::Cics(code) | Self::User(code) => code,
177 Self::Le(_) => "U4038",
178 Self::Requested { code, .. } => code,
179 Self::Ironwork => "IRONWORK",
180 Self::Exec => "EXEC",
181 Self::Sql => "SQL",
182 Self::SqlReplay => "SQLR",
183 Self::Java => "JAVA",
184 Self::Signal(s) => s.text(),
185 }
186 }
187
188 pub fn bypasses_trap_off(&self) -> bool {
191 matches!(self, Self::Check(_) | Self::Protection | Self::TimeLimit)
192 }
193
194 pub fn skips_termination(&self, trap_off: bool) -> bool {
199 match self {
200 Self::Requested { clean_up, .. } => !clean_up || trap_off,
201 _ => trap_off && self.bypasses_trap_off(),
202 }
203 }
204
205 pub fn size_error(&self) -> bool {
208 matches!(self, Self::Check(ProgramCheck::DecimalDivide | ProgramCheck::FixedPointDivide | ProgramCheck::HfpDivide) | Self::Le(LeCondition::ZeroPower))
209 }
210}
211
212impl fmt::Display for AbendCode {
213 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214 f.write_str(self.as_str())
215 }
216}
217
218impl PartialEq<&str> for AbendCode {
219 fn eq(&self, other: &&str) -> bool {
220 self.as_str() == *other
221 }
222}
223
224impl From<&str> for AbendCode {
226 fn from(text: &str) -> Self {
227 let user = text.len() == 5 && text.starts_with('U') && text[1..].bytes().all(|b| b.is_ascii_digit());
228 CHECKS
229 .map(Self::Check)
230 .into_iter()
231 .chain([Self::Protection, Self::ModuleNotFound, Self::TimeLimit, Self::Ironwork, Self::Exec, Self::Sql, Self::SqlReplay, Self::Java])
232 .chain(FileStatus::ALL.map(Self::Io))
233 .chain(Signal::ALL.map(Self::Signal))
234 .find(|c| c.as_str() == text)
235 .unwrap_or_else(|| if user { Self::User(text.into()) } else { Self::Cics(text.into()) })
236 }
237}
238
239#[derive(Clone, Copy, Debug, PartialEq, Eq)]
241pub enum FileStatus {
242 Success,
243 SuccessDuplicate,
244 SuccessWrongLength,
245 SuccessOptional,
246 AtEnd,
247 RelativeKeyOverflow,
248 SequenceError,
249 DuplicateKey,
250 NotFound,
251 BoundaryViolation,
252 PermanentError,
253 FileNotFound,
254 OpenModeUnsupported,
255 AlreadyOpen,
256 NotOpen,
257 NoPriorRead,
258 RecordLengthChanged,
259 NoNextRecord,
260 NotOpenInput,
261 NotOpenOutput,
262 NotOpenInputOutput,
263 SuccessNonReel,
265 ClosedWithLock,
266 SuccessVerified,
268}
269
270impl FileStatus {
271 pub(crate) const ALL: [Self; 24] = [
272 Self::Success,
273 Self::SuccessDuplicate,
274 Self::SuccessWrongLength,
275 Self::SuccessOptional,
276 Self::AtEnd,
277 Self::RelativeKeyOverflow,
278 Self::SequenceError,
279 Self::DuplicateKey,
280 Self::NotFound,
281 Self::BoundaryViolation,
282 Self::PermanentError,
283 Self::FileNotFound,
284 Self::OpenModeUnsupported,
285 Self::AlreadyOpen,
286 Self::NotOpen,
287 Self::NoPriorRead,
288 Self::RecordLengthChanged,
289 Self::NoNextRecord,
290 Self::NotOpenInput,
291 Self::NotOpenOutput,
292 Self::NotOpenInputOutput,
293 Self::SuccessNonReel,
294 Self::ClosedWithLock,
295 Self::SuccessVerified,
296 ];
297
298 pub fn abend_code(self) -> &'static str {
300 match self {
301 Self::Success => "IO-00",
302 Self::SuccessDuplicate => "IO-02",
303 Self::SuccessWrongLength => "IO-04",
304 Self::SuccessOptional => "IO-05",
305 Self::SuccessNonReel => "IO-07",
306 Self::AtEnd => "IO-10",
307 Self::RelativeKeyOverflow => "IO-14",
308 Self::SequenceError => "IO-21",
309 Self::DuplicateKey => "IO-22",
310 Self::NotFound => "IO-23",
311 Self::BoundaryViolation => "IO-24",
312 Self::PermanentError => "IO-30",
313 Self::FileNotFound => "IO-35",
314 Self::OpenModeUnsupported => "IO-37",
315 Self::ClosedWithLock => "IO-38",
316 Self::AlreadyOpen => "IO-41",
317 Self::NotOpen => "IO-42",
318 Self::NoPriorRead => "IO-43",
319 Self::RecordLengthChanged => "IO-44",
320 Self::NoNextRecord => "IO-46",
321 Self::NotOpenInput => "IO-47",
322 Self::NotOpenOutput => "IO-48",
323 Self::NotOpenInputOutput => "IO-49",
324 Self::SuccessVerified => "IO-97",
325 }
326 }
327
328 pub fn as_str(self) -> &'static str {
329 &self.abend_code()["IO-".len()..]
330 }
331
332 pub fn covers(self, class: char) -> bool {
334 self.as_str().starts_with(class)
335 }
336
337 pub fn ends_the_run(self) -> bool {
340 self != Self::SuccessVerified
341 }
342
343 pub fn meaning(self) -> &'static str {
345 match self {
346 Self::AtEnd => "there is no next record",
347 Self::SequenceError => "the key is out of sequence",
348 Self::DuplicateKey => "a record with that key is already there",
349 Self::NotFound => "there is no record with that key",
350 Self::RelativeKeyOverflow => "the record number is too large for the RELATIVE KEY",
351 Self::BoundaryViolation => "the record number is outside the file",
352 Self::NoPriorRead => "the last statement on the file was not a successful READ",
353 Self::RecordLengthChanged => "the record is not the length of the one it replaces, or outside the lengths RECORD IS VARYING allows",
354 Self::NoNextRecord => "there is no next record: the last READ reached the end, or START found nothing",
355 Self::NotOpenInput => "the file is not open INPUT or I-O",
356 Self::NotOpenOutput => "the file is not open for output",
357 Self::NotOpenInputOutput => "the file is not open I-O",
358 Self::ClosedWithLock => "the file was closed WITH LOCK",
359 _ => "the statement failed",
360 }
361 }
362
363 pub fn cics_condition(self) -> Condition {
365 match self {
366 Self::DuplicateKey => Condition::DUPREC,
367 Self::NotFound => Condition::NOTFND,
368 _ => Condition::INVREQ,
369 }
370 }
371}
372
373impl From<&str> for FileStatus {
375 fn from(code: &str) -> Self {
376 Self::ALL.into_iter().find(|s| s.as_str() == code).unwrap_or_else(|| panic!("{code} is not a file status ironwork sets"))
377 }
378}
379
380#[cfg(test)]
381mod tests {
382 use super::*;
383
384 #[test]
385 fn each_code_prints_as_the_string_it_replaced_and_parses_back_from_it() {
386 for c in CHECKS {
387 assert_eq!(AbendCode::Check(c).to_string(), c.abend());
388 }
389 for text in ["S0C7", "S0C4", "S806", "S322", "IO-35", "IO-46", "AEIP", "ASRA", "0999", "U4038", "IRONWORK", "EXEC", "SQL", "SQLR", "JAVA", "SORT-STOPPED", "CLOSED-OUTPUT"] {
390 assert_eq!(AbendCode::from(text).to_string(), text);
391 }
392 assert_eq!(AbendCode::from("U4038"), AbendCode::user(4038));
393 assert_eq!(AbendCode::from("IO-46"), AbendCode::Io(FileStatus::NoNextRecord));
394 assert_eq!(AbendCode::from("SORT-STOPPED"), AbendCode::Signal(Signal::SortStopped));
395 }
396
397 #[test]
398 fn a_condition_another_statement_takes_is_known_by_its_code_not_its_text() {
399 let at = Pos::default();
400 assert!(Abend::zero_power(at).size_error());
401 assert!(Abend::check(ProgramCheck::DecimalDivide, at).size_error());
402 let reworded = Abend { message: "zero to the power -1".into(), ..Abend::zero_power(at) };
403 assert!(reworded.size_error());
404 let text_alone = Abend { code: AbendCode::user(4038), ..Abend::zero_power(at) };
405 assert!(!text_alone.size_error());
406 assert!(!Abend { code: AbendCode::Le(LeCondition::FileStatus), ..Abend::zero_power(at) }.size_error());
407 let file_status = |code, message: &str| crate::fileio::is_unhandled_io(&Abend { code, message: message.into(), pos: at, file: None });
408 assert!(file_status(AbendCode::Le(LeCondition::FileStatus), "a status no FILE STATUS took"));
409 assert!(file_status(AbendCode::Io(FileStatus::PermanentError), ""));
410 assert!(!file_status(AbendCode::user(4038), "IGZ0035S There was an unsuccessful OPEN or CLOSE of file F."));
411 for c in [LeCondition::ZeroPower, LeCondition::FileStatus] {
412 assert_eq!(AbendCode::Le(c), "U4038");
413 }
414 }
415}