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 {
23 Self { code: AbendCode::Check(c), message: format!("{c:?} exception"), pos, file: None }
24 }
25
26 pub fn ironwork(message: impl Into<String>, pos: Pos) -> Self {
27 Self { code: AbendCode::Ironwork, message: message.into(), pos, file: None }
28 }
29}
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq)]
32pub enum Ending {
33 Goback,
34 StopRun,
35 EndOfProgram,
36}
37
38#[derive(Clone, Debug, PartialEq, Eq)]
39pub enum AbendCode {
40 Check(ProgramCheck),
42 Protection,
44 ModuleNotFound,
46 TimeLimit,
48 Io(FileStatus),
50 Cics(String),
52 User(String),
54 Ironwork,
55 Exec,
56 Sql,
57 SqlReplay,
59 Java,
60 Signal(Signal),
61}
62
63#[derive(Clone, Copy, Debug, PartialEq, Eq)]
65pub enum Signal {
66 StopRun,
69 GoBack,
71 SortStopped,
73 ClosedOutput,
75 DeclarativeExit,
78}
79
80impl Signal {
81 const ALL: [Self; 5] = [Self::StopRun, Self::GoBack, Self::SortStopped, Self::ClosedOutput, Self::DeclarativeExit];
82
83 fn text(self) -> &'static str {
84 match self {
85 Self::StopRun => "REPORT-STOP-RUN",
86 Self::GoBack => "REPORT-GOBACK",
87 Self::SortStopped => "SORT-STOPPED",
88 Self::ClosedOutput => "CLOSED-OUTPUT",
89 Self::DeclarativeExit => "DECLARATIVE-EXIT",
90 }
91 }
92}
93
94const CHECKS: [ProgramCheck; 10] = [
95 ProgramCheck::Specification,
96 ProgramCheck::Data,
97 ProgramCheck::FixedPointOverflow,
98 ProgramCheck::FixedPointDivide,
99 ProgramCheck::DecimalOverflow,
100 ProgramCheck::DecimalDivide,
101 ProgramCheck::HfpExponentOverflow,
102 ProgramCheck::HfpExponentUnderflow,
103 ProgramCheck::HfpSignificance,
104 ProgramCheck::HfpDivide,
105];
106
107fn check_code(c: ProgramCheck) -> &'static str {
108 match c {
109 ProgramCheck::Specification => "S0C6",
110 ProgramCheck::Data => "S0C7",
111 ProgramCheck::FixedPointOverflow => "S0C8",
112 ProgramCheck::FixedPointDivide => "S0C9",
113 ProgramCheck::DecimalOverflow => "S0CA",
114 ProgramCheck::DecimalDivide => "S0CB",
115 ProgramCheck::HfpExponentOverflow => "S0CC",
116 ProgramCheck::HfpExponentUnderflow => "S0CD",
117 ProgramCheck::HfpSignificance => "S0CE",
118 ProgramCheck::HfpDivide => "S0CF",
119 }
120}
121
122impl AbendCode {
123 pub fn user(code: u16) -> Self {
124 Self::User(format!("U{code:04}"))
125 }
126
127 pub fn as_str(&self) -> &str {
128 match self {
129 Self::Check(c) => check_code(*c),
130 Self::Protection => "S0C4",
131 Self::ModuleNotFound => "S806",
132 Self::TimeLimit => "S322",
133 Self::Io(status) => status.abend_code(),
134 Self::Cics(code) | Self::User(code) => code,
135 Self::Ironwork => "IRONWORK",
136 Self::Exec => "EXEC",
137 Self::Sql => "SQL",
138 Self::SqlReplay => "SQLR",
139 Self::Java => "JAVA",
140 Self::Signal(s) => s.text(),
141 }
142 }
143
144 pub fn bypasses_trap_off(&self) -> bool {
147 matches!(self, Self::Check(_) | Self::Protection | Self::TimeLimit)
148 }
149
150 pub fn zero_divisor(&self) -> bool {
153 matches!(self, Self::Check(ProgramCheck::DecimalDivide | ProgramCheck::FixedPointDivide | ProgramCheck::HfpDivide))
154 }
155}
156
157impl fmt::Display for AbendCode {
158 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159 f.write_str(self.as_str())
160 }
161}
162
163impl PartialEq<&str> for AbendCode {
164 fn eq(&self, other: &&str) -> bool {
165 self.as_str() == *other
166 }
167}
168
169impl From<&str> for AbendCode {
171 fn from(text: &str) -> Self {
172 let user = text.len() == 5 && text.starts_with('U') && text[1..].bytes().all(|b| b.is_ascii_digit());
173 CHECKS
174 .map(Self::Check)
175 .into_iter()
176 .chain([Self::Protection, Self::ModuleNotFound, Self::TimeLimit, Self::Ironwork, Self::Exec, Self::Sql, Self::SqlReplay, Self::Java])
177 .chain(FileStatus::ALL.map(Self::Io))
178 .chain(Signal::ALL.map(Self::Signal))
179 .find(|c| c.as_str() == text)
180 .unwrap_or_else(|| if user { Self::User(text.into()) } else { Self::Cics(text.into()) })
181 }
182}
183
184#[derive(Clone, Copy, Debug, PartialEq, Eq)]
186pub enum FileStatus {
187 Success,
188 SuccessDuplicate,
189 SuccessWrongLength,
190 SuccessOptional,
191 AtEnd,
192 RelativeKeyOverflow,
193 SequenceError,
194 DuplicateKey,
195 NotFound,
196 BoundaryViolation,
197 PermanentError,
198 FileNotFound,
199 OpenModeUnsupported,
200 AlreadyOpen,
201 NotOpen,
202 NoPriorRead,
203 RecordLengthChanged,
204 NoNextRecord,
205 NotOpenInput,
206 NotOpenOutput,
207 NotOpenInputOutput,
208 SuccessNonReel,
210 ClosedWithLock,
211 SuccessVerified,
213}
214
215impl FileStatus {
216 pub(crate) const ALL: [Self; 24] = [
217 Self::Success,
218 Self::SuccessDuplicate,
219 Self::SuccessWrongLength,
220 Self::SuccessOptional,
221 Self::AtEnd,
222 Self::RelativeKeyOverflow,
223 Self::SequenceError,
224 Self::DuplicateKey,
225 Self::NotFound,
226 Self::BoundaryViolation,
227 Self::PermanentError,
228 Self::FileNotFound,
229 Self::OpenModeUnsupported,
230 Self::AlreadyOpen,
231 Self::NotOpen,
232 Self::NoPriorRead,
233 Self::RecordLengthChanged,
234 Self::NoNextRecord,
235 Self::NotOpenInput,
236 Self::NotOpenOutput,
237 Self::NotOpenInputOutput,
238 Self::SuccessNonReel,
239 Self::ClosedWithLock,
240 Self::SuccessVerified,
241 ];
242
243 pub fn abend_code(self) -> &'static str {
245 match self {
246 Self::Success => "IO-00",
247 Self::SuccessDuplicate => "IO-02",
248 Self::SuccessWrongLength => "IO-04",
249 Self::SuccessOptional => "IO-05",
250 Self::SuccessNonReel => "IO-07",
251 Self::AtEnd => "IO-10",
252 Self::RelativeKeyOverflow => "IO-14",
253 Self::SequenceError => "IO-21",
254 Self::DuplicateKey => "IO-22",
255 Self::NotFound => "IO-23",
256 Self::BoundaryViolation => "IO-24",
257 Self::PermanentError => "IO-30",
258 Self::FileNotFound => "IO-35",
259 Self::OpenModeUnsupported => "IO-37",
260 Self::ClosedWithLock => "IO-38",
261 Self::AlreadyOpen => "IO-41",
262 Self::NotOpen => "IO-42",
263 Self::NoPriorRead => "IO-43",
264 Self::RecordLengthChanged => "IO-44",
265 Self::NoNextRecord => "IO-46",
266 Self::NotOpenInput => "IO-47",
267 Self::NotOpenOutput => "IO-48",
268 Self::NotOpenInputOutput => "IO-49",
269 Self::SuccessVerified => "IO-97",
270 }
271 }
272
273 pub fn as_str(self) -> &'static str {
274 &self.abend_code()["IO-".len()..]
275 }
276
277 pub fn covers(self, class: char) -> bool {
279 self.as_str().starts_with(class)
280 }
281
282 pub fn ends_the_run(self) -> bool {
285 self != Self::SuccessVerified
286 }
287
288 pub fn meaning(self) -> &'static str {
290 match self {
291 Self::AtEnd => "there is no next record",
292 Self::SequenceError => "the key is out of sequence",
293 Self::DuplicateKey => "a record with that key is already there",
294 Self::NotFound => "there is no record with that key",
295 Self::RelativeKeyOverflow => "the record number is too large for the RELATIVE KEY",
296 Self::BoundaryViolation => "the record number is outside the file",
297 Self::NoPriorRead => "the last statement on the file was not a successful READ",
298 Self::RecordLengthChanged => "the record is not the length of the one it replaces, or outside the lengths RECORD IS VARYING allows",
299 Self::NoNextRecord => "there is no next record: the last READ reached the end, or START found nothing",
300 Self::NotOpenInput => "the file is not open INPUT or I-O",
301 Self::NotOpenOutput => "the file is not open for output",
302 Self::NotOpenInputOutput => "the file is not open I-O",
303 Self::ClosedWithLock => "the file was closed WITH LOCK",
304 _ => "the statement failed",
305 }
306 }
307
308 pub fn cics_condition(self) -> Condition {
310 match self {
311 Self::DuplicateKey => Condition::DUPREC,
312 Self::NotFound => Condition::NOTFND,
313 _ => Condition::INVREQ,
314 }
315 }
316}
317
318impl From<&str> for FileStatus {
320 fn from(code: &str) -> Self {
321 Self::ALL.into_iter().find(|s| s.as_str() == code).unwrap_or_else(|| panic!("{code} is not a file status ironwork sets"))
322 }
323}
324
325#[cfg(test)]
326mod tests {
327 use super::*;
328
329 #[test]
330 fn each_code_prints_as_the_string_it_replaced_and_parses_back_from_it() {
331 for c in CHECKS {
332 assert_eq!(AbendCode::Check(c).to_string(), c.abend());
333 }
334 for text in ["S0C7", "S0C4", "S806", "S322", "IO-35", "IO-46", "AEIP", "ASRA", "0999", "U4038", "IRONWORK", "EXEC", "SQL", "SQLR", "JAVA", "SORT-STOPPED", "CLOSED-OUTPUT"] {
335 assert_eq!(AbendCode::from(text).to_string(), text);
336 }
337 assert_eq!(AbendCode::from("U4038"), AbendCode::user(4038));
338 assert_eq!(AbendCode::from("IO-46"), AbendCode::Io(FileStatus::NoNextRecord));
339 assert_eq!(AbendCode::from("SORT-STOPPED"), AbendCode::Signal(Signal::SortStopped));
340 }
341}