Skip to main content

ironwork_numeric/
options.rs

1use std::fmt;
2use zarch::ebcdic::CodePage;
3use zarch::hfp::Precision;
4
5#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
6pub enum Arith {
7    #[default]
8    Compat,
9    Extend,
10}
11
12impl Arith {
13    pub const fn max_picture_digits(self) -> u32 {
14        match self {
15            Self::Compat => 18,
16            Self::Extend => 31,
17        }
18    }
19
20    pub const fn intermediate_digits(self) -> u32 {
21        match self {
22            Self::Compat => 30,
23            Self::Extend => 31,
24        }
25    }
26
27    pub const fn float_intermediate(self) -> Precision {
28        match self {
29            Self::Compat => Precision::Long,
30            Self::Extend => Precision::Extended,
31        }
32    }
33}
34
35#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
36pub enum Trunc {
37    #[default]
38    Std,
39    Opt,
40    Bin,
41}
42
43#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
44pub enum Numproc {
45    #[default]
46    Nopfd,
47    Pfd,
48}
49
50/// Whether a store that TRUNC(OPT) leaves to the generated code is reported when decimal and binary
51/// truncation disagree. `-silent` turns the reports off.
52#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
53pub enum TruncCheck {
54    #[default]
55    Report,
56    Silent,
57}
58
59#[derive(Clone, Copy, Debug, PartialEq, Eq)]
60pub struct Options {
61    pub arith: Arith,
62    pub trunc: Trunc,
63    pub numproc: Numproc,
64    pub codepage: u16,
65    pub trunc_check: TruncCheck,
66}
67
68impl Default for Options {
69    fn default() -> Self {
70        Self { arith: Arith::default(), trunc: Trunc::default(), numproc: Numproc::default(), codepage: 1140, trunc_check: TruncCheck::default() }
71    }
72}
73
74#[derive(Clone, Debug, PartialEq, Eq)]
75pub enum OptionError {
76    BadSuboption { option: String, given: String },
77    Removed { option: String, since: &'static str },
78    UnsupportedCodePage(u16),
79    UnknownFlag(String),
80}
81
82impl fmt::Display for OptionError {
83    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84        match self {
85            Self::BadSuboption { option, given } => write!(f, "{option} does not take ({given})"),
86            Self::Removed { option, since } => write!(f, "{option} was removed in Enterprise COBOL {since}"),
87            Self::UnsupportedCodePage(ccsid) => write!(f, "CODEPAGE({ccsid}) is not a single-byte EBCDIC page this compiler carries"),
88            Self::UnknownFlag(flag) => write!(f, "unknown flag {flag}"),
89        }
90    }
91}
92
93impl std::error::Error for OptionError {}
94
95impl Options {
96    /// Applies one IBM compiler option as a CBL or PROCESS card or PARM writes it, e.g.
97    /// `TRUNC(OPT)`, `AR(E)`, `CP(1047)`. Returns false for an option this layer does not read.
98    pub fn apply(&mut self, option: &str) -> Result<bool, OptionError> {
99        let option = option.trim().to_ascii_uppercase();
100        let (name, sub) = match option.split_once('(') {
101            Some((name, rest)) => (name.trim(), rest.trim_end_matches(')').trim()),
102            None => (option.as_str(), ""),
103        };
104        let bad = || OptionError::BadSuboption { option: name.to_owned(), given: sub.to_owned() };
105        match name {
106            "ARITH" | "AR" => {
107                self.arith = match sub {
108                    "COMPAT" | "C" => Arith::Compat,
109                    "EXTEND" | "E" => Arith::Extend,
110                    _ => return Err(bad()),
111                }
112            }
113            "TRUNC" => {
114                self.trunc = match sub {
115                    "STD" => Trunc::Std,
116                    "OPT" => Trunc::Opt,
117                    "BIN" => Trunc::Bin,
118                    _ => return Err(bad()),
119                }
120            }
121            "NUMPROC" => {
122                self.numproc = match sub {
123                    "NOPFD" => Numproc::Nopfd,
124                    "PFD" => Numproc::Pfd,
125                    "MIG" => return Err(OptionError::Removed { option: "NUMPROC(MIG)".into(), since: "V5" }),
126                    _ => return Err(bad()),
127                }
128            }
129            "CODEPAGE" | "CP" => {
130                let ccsid: u16 = sub.parse().map_err(|_| bad())?;
131                CodePage::by_ccsid(ccsid).ok_or(OptionError::UnsupportedCodePage(ccsid))?;
132                self.codepage = ccsid;
133            }
134            _ => return Ok(false),
135        }
136        Ok(true)
137    }
138
139    /// Applies a flag of this compiler's own command line.
140    pub fn apply_flag(&mut self, flag: &str) -> Result<(), OptionError> {
141        match flag {
142            "-silent" => self.trunc_check = TruncCheck::Silent,
143            _ => return Err(OptionError::UnknownFlag(flag.to_owned())),
144        }
145        Ok(())
146    }
147
148    pub fn code_page(&self) -> &'static CodePage {
149        CodePage::by_ccsid(self.codepage).expect("codepage validated when applied")
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    #[test]
158    fn defaults_are_ibms() {
159        let o = Options::default();
160        assert_eq!((o.arith, o.trunc, o.numproc, o.codepage), (Arith::Compat, Trunc::Std, Numproc::Nopfd, 1140));
161    }
162
163    #[test]
164    fn abbreviations_and_case() {
165        let mut o = Options::default();
166        assert_eq!(o.apply("ar(e)"), Ok(true));
167        assert_eq!(o.apply("CP(1047)"), Ok(true));
168        assert_eq!(o.apply("TRUNC(BIN)"), Ok(true));
169        assert_eq!((o.arith, o.codepage, o.trunc), (Arith::Extend, 1047, Trunc::Bin));
170    }
171
172    #[test]
173    fn options_this_layer_does_not_read_pass_through() {
174        assert_eq!(Options::default().apply("SSRANGE"), Ok(false));
175    }
176
177    #[test]
178    fn refusals_name_the_problem() {
179        let mut o = Options::default();
180        assert!(matches!(o.apply("NUMPROC(MIG)"), Err(OptionError::Removed { .. })));
181        assert!(matches!(o.apply("TRUNC(FAST)"), Err(OptionError::BadSuboption { .. })));
182        assert_eq!(o.apply("CODEPAGE(930)"), Err(OptionError::UnsupportedCodePage(930)));
183    }
184
185    #[test]
186    fn silent_flag_turns_off_trunc_reports() {
187        let mut o = Options::default();
188        o.apply_flag("-silent").unwrap();
189        assert_eq!(o.trunc_check, TruncCheck::Silent);
190        assert!(o.apply_flag("-quiet").is_err());
191    }
192}