use crate::cics::Condition;
use crate::vocab::Pos;
use std::fmt;
use zarch::check::ProgramCheck;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Abend {
pub code: AbendCode,
pub message: String,
pub pos: Pos,
}
impl Abend {
pub fn check(c: ProgramCheck, pos: Pos) -> Self {
Self { code: AbendCode::Check(c), message: format!("{c:?} exception"), pos }
}
pub fn ironwork(message: impl Into<String>, pos: Pos) -> Self {
Self { code: AbendCode::Ironwork, message: message.into(), pos }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Ending {
Goback,
StopRun,
EndOfProgram,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AbendCode {
Check(ProgramCheck),
Protection,
ModuleNotFound,
Io(FileStatus),
Cics(String),
User(String),
Ironwork,
Exec,
Sql,
SqlReplay,
Java,
Signal(Signal),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Signal {
StopRun,
GoBack,
SortStopped,
ClosedOutput,
DeclarativeExit,
}
impl Signal {
const ALL: [Self; 5] = [Self::StopRun, Self::GoBack, Self::SortStopped, Self::ClosedOutput, Self::DeclarativeExit];
fn text(self) -> &'static str {
match self {
Self::StopRun => "REPORT-STOP-RUN",
Self::GoBack => "REPORT-GOBACK",
Self::SortStopped => "SORT-STOPPED",
Self::ClosedOutput => "CLOSED-OUTPUT",
Self::DeclarativeExit => "DECLARATIVE-EXIT",
}
}
}
const CHECKS: [ProgramCheck; 10] = [
ProgramCheck::Specification,
ProgramCheck::Data,
ProgramCheck::FixedPointOverflow,
ProgramCheck::FixedPointDivide,
ProgramCheck::DecimalOverflow,
ProgramCheck::DecimalDivide,
ProgramCheck::HfpExponentOverflow,
ProgramCheck::HfpExponentUnderflow,
ProgramCheck::HfpSignificance,
ProgramCheck::HfpDivide,
];
fn check_code(c: ProgramCheck) -> &'static str {
match c {
ProgramCheck::Specification => "S0C6",
ProgramCheck::Data => "S0C7",
ProgramCheck::FixedPointOverflow => "S0C8",
ProgramCheck::FixedPointDivide => "S0C9",
ProgramCheck::DecimalOverflow => "S0CA",
ProgramCheck::DecimalDivide => "S0CB",
ProgramCheck::HfpExponentOverflow => "S0CC",
ProgramCheck::HfpExponentUnderflow => "S0CD",
ProgramCheck::HfpSignificance => "S0CE",
ProgramCheck::HfpDivide => "S0CF",
}
}
impl AbendCode {
pub fn user(code: u16) -> Self {
Self::User(format!("U{code:04}"))
}
pub fn as_str(&self) -> &str {
match self {
Self::Check(c) => check_code(*c),
Self::Protection => "S0C4",
Self::ModuleNotFound => "S806",
Self::Io(status) => status.abend_code(),
Self::Cics(code) | Self::User(code) => code,
Self::Ironwork => "IRONWORK",
Self::Exec => "EXEC",
Self::Sql => "SQL",
Self::SqlReplay => "SQLR",
Self::Java => "JAVA",
Self::Signal(s) => s.text(),
}
}
pub fn starts_with(&self, prefix: &str) -> bool {
self.as_str().starts_with(prefix)
}
pub fn zero_divisor(&self) -> bool {
matches!(self, Self::Check(ProgramCheck::DecimalDivide | ProgramCheck::FixedPointDivide | ProgramCheck::HfpDivide))
}
}
impl fmt::Display for AbendCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl PartialEq<&str> for AbendCode {
fn eq(&self, other: &&str) -> bool {
self.as_str() == *other
}
}
impl From<&str> for AbendCode {
fn from(text: &str) -> Self {
let user = text.len() == 5 && text.starts_with('U') && text[1..].bytes().all(|b| b.is_ascii_digit());
CHECKS
.map(Self::Check)
.into_iter()
.chain([Self::Protection, Self::ModuleNotFound, Self::Ironwork, Self::Exec, Self::Sql, Self::SqlReplay, Self::Java])
.chain(FileStatus::ALL.map(Self::Io))
.chain(Signal::ALL.map(Self::Signal))
.find(|c| c.as_str() == text)
.unwrap_or_else(|| if user { Self::User(text.into()) } else { Self::Cics(text.into()) })
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FileStatus {
Success,
SuccessDuplicate,
SuccessWrongLength,
SuccessOptional,
AtEnd,
RelativeKeyOverflow,
SequenceError,
DuplicateKey,
NotFound,
BoundaryViolation,
PermanentError,
FileNotFound,
OpenModeUnsupported,
AlreadyOpen,
NotOpen,
NoPriorRead,
RecordLengthChanged,
NoNextRecord,
NotOpenInput,
NotOpenOutput,
NotOpenInputOutput,
}
impl FileStatus {
pub(crate) const ALL: [Self; 21] = [
Self::Success,
Self::SuccessDuplicate,
Self::SuccessWrongLength,
Self::SuccessOptional,
Self::AtEnd,
Self::RelativeKeyOverflow,
Self::SequenceError,
Self::DuplicateKey,
Self::NotFound,
Self::BoundaryViolation,
Self::PermanentError,
Self::FileNotFound,
Self::OpenModeUnsupported,
Self::AlreadyOpen,
Self::NotOpen,
Self::NoPriorRead,
Self::RecordLengthChanged,
Self::NoNextRecord,
Self::NotOpenInput,
Self::NotOpenOutput,
Self::NotOpenInputOutput,
];
pub fn abend_code(self) -> &'static str {
match self {
Self::Success => "IO-00",
Self::SuccessDuplicate => "IO-02",
Self::SuccessWrongLength => "IO-04",
Self::SuccessOptional => "IO-05",
Self::AtEnd => "IO-10",
Self::RelativeKeyOverflow => "IO-14",
Self::SequenceError => "IO-21",
Self::DuplicateKey => "IO-22",
Self::NotFound => "IO-23",
Self::BoundaryViolation => "IO-24",
Self::PermanentError => "IO-30",
Self::FileNotFound => "IO-35",
Self::OpenModeUnsupported => "IO-37",
Self::AlreadyOpen => "IO-41",
Self::NotOpen => "IO-42",
Self::NoPriorRead => "IO-43",
Self::RecordLengthChanged => "IO-44",
Self::NoNextRecord => "IO-46",
Self::NotOpenInput => "IO-47",
Self::NotOpenOutput => "IO-48",
Self::NotOpenInputOutput => "IO-49",
}
}
pub fn as_str(self) -> &'static str {
&self.abend_code()["IO-".len()..]
}
pub fn covers(self, class: char) -> bool {
self.as_str().starts_with(class)
}
pub fn meaning(self) -> &'static str {
match self {
Self::AtEnd => "there is no next record",
Self::SequenceError => "the key is out of sequence",
Self::DuplicateKey => "a record with that key is already there",
Self::NotFound => "there is no record with that key",
Self::RelativeKeyOverflow => "the record number is too large for the RELATIVE KEY",
Self::BoundaryViolation => "the record number is outside the file",
Self::NoPriorRead => "the last statement on the file was not a successful READ",
Self::RecordLengthChanged => "the record is not the length of the one it replaces",
Self::NoNextRecord => "there is no next record: the last READ reached the end, or START found nothing",
Self::NotOpenInput => "the file is not open INPUT or I-O",
Self::NotOpenOutput => "the file is not open for output",
Self::NotOpenInputOutput => "the file is not open I-O",
_ => "the statement failed",
}
}
pub fn cics_condition(self) -> Condition {
match self {
Self::DuplicateKey => Condition::DUPREC,
Self::NotFound => Condition::NOTFND,
_ => Condition::INVREQ,
}
}
}
impl From<&str> for FileStatus {
fn from(code: &str) -> Self {
Self::ALL.into_iter().find(|s| s.as_str() == code).unwrap_or_else(|| panic!("{code} is not a file status ironwork sets"))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn each_code_prints_as_the_string_it_replaced_and_parses_back_from_it() {
for c in CHECKS {
assert_eq!(AbendCode::Check(c).to_string(), c.abend());
}
for text in ["S0C7", "S0C4", "S806", "IO-35", "IO-46", "AEIP", "ASRA", "0999", "U4038", "IRONWORK", "EXEC", "SQL", "SQLR", "JAVA", "SORT-STOPPED", "CLOSED-OUTPUT"] {
assert_eq!(AbendCode::from(text).to_string(), text);
}
assert_eq!(AbendCode::from("U4038"), AbendCode::user(4038));
assert_eq!(AbendCode::from("IO-46"), AbendCode::Io(FileStatus::NoNextRecord));
assert_eq!(AbendCode::from("SORT-STOPPED"), AbendCode::Signal(Signal::SortStopped));
}
}