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