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